add 'Scroll to the end of the conversation' bar
Change-Id: I63e41433db4999d021bc13375cf5b7ba07843c3d
diff --git a/client/package.json b/client/package.json
index 875bf02..523d9f1 100644
--- a/client/package.json
+++ b/client/package.json
@@ -57,6 +57,7 @@
"react-modal": "^3.15.1",
"react-redux": "^8.0.2",
"react-router-dom": "^6.3.0",
+ "react-waypoint": "^10.3.0",
"socket.io-client": "^4.5.2"
},
"devDependencies": {
diff --git a/client/src/components/ConversationView.tsx b/client/src/components/ConversationView.tsx
index a269c89..1242541 100644
--- a/client/src/components/ConversationView.tsx
+++ b/client/src/components/ConversationView.tsx
@@ -101,31 +101,25 @@
return (
<Stack height="100%">
- <Stack padding="16px">
- <ConversationHeader
- account={account}
- members={conversation.getMembers()}
- adminTitle={conversation.infos.title as string}
- conversationId={conversationId}
- />
- </Stack>
+ <ConversationHeader
+ account={account}
+ members={conversation.getMembers()}
+ adminTitle={conversation.infos.title as string}
+ conversationId={conversationId}
+ />
<Divider
sx={{
borderTop: '1px solid #E5E5E5',
}}
/>
- <Stack flex={1} overflow="auto" direction="column-reverse" padding="0px 16px">
- <MessageList account={account} members={conversation.getMembers()} messages={messages} />
- </Stack>
+ <MessageList account={account} members={conversation.getMembers()} messages={messages} />
<Divider
sx={{
margin: '30px 16px 0px 16px',
borderTop: '1px solid #E5E5E5',
}}
/>
- <Stack padding="16px">
- <SendMessageForm account={account} members={conversation.getMembers()} onSend={sendMessage} />
- </Stack>
+ <SendMessageForm account={account} members={conversation.getMembers()} onSend={sendMessage} />
</Stack>
);
};
@@ -173,7 +167,7 @@
};
return (
- <Stack direction="row">
+ <Stack direction="row" padding="16px">
<Stack flex={1} justifyContent="center" whiteSpace="nowrap" overflow="hidden">
<Typography variant="h3" textOverflow="ellipsis">
{title}
diff --git a/client/src/components/MessageList.tsx b/client/src/components/MessageList.tsx
index 6e5e233..7b9bf2d 100644
--- a/client/src/components/MessageList.tsx
+++ b/client/src/components/MessageList.tsx
@@ -15,10 +15,15 @@
* License along with this program. If not, see
* <https://www.gnu.org/licenses/>.
*/
-import { Stack } from '@mui/system';
+import { Typography } from '@mui/material';
+import { Box, Stack } from '@mui/system';
import { Account, ConversationMember, Message } from 'jami-web-common';
+import { MutableRefObject, useRef, useState } from 'react';
+import { useTranslation } from 'react-i18next';
+import { Waypoint } from 'react-waypoint';
import { MessageRow } from './Message';
+import { ArrowDownIcon } from './SvgIcon';
interface MessageListProps {
account: Account;
@@ -27,31 +32,82 @@
}
export default function MessageList({ account, members, messages }: MessageListProps) {
+ const [showScrollButton, setShowScrollButton] = useState(false);
+ const listBottomRef = useRef<HTMLElement>();
+
return (
- <Stack direction="column-reverse">
- {
- // most recent messages first
- messages.map((message, index) => {
- const isAccountMessage = message.author === account.getUri();
- let author;
- if (isAccountMessage) {
- author = account;
- } else {
- const member = members.find((member) => message.author === member.contact.getUri());
- author = member?.contact;
+ <>
+ <Stack flex={1} overflow="auto" padding="0px 16px" direction="column-reverse">
+ {/* Here is the bottom of the list of messages because of 'column-reverse' */}
+ <Box ref={listBottomRef} />
+ <Waypoint
+ onEnter={() => setShowScrollButton(false)}
+ onLeave={() => setShowScrollButton(true)}
+ bottomOffset="-100px"
+ />
+ <Stack direction="column-reverse">
+ {
+ // most recent messages first
+ messages.map((message, index) => {
+ const isAccountMessage = message.author === account.getUri();
+ let author;
+ if (isAccountMessage) {
+ author = account;
+ } else {
+ const member = members.find((member) => message.author === member.contact.getUri());
+ author = member?.contact;
+ }
+ if (!author) {
+ return null;
+ }
+ const props = {
+ messageIndex: index,
+ messages,
+ isAccountMessage,
+ author,
+ };
+ return <MessageRow key={message.id} {...props} />;
+ })
}
- if (!author) {
- return null;
- }
- const props = {
- messageIndex: index,
- messages,
- isAccountMessage,
- author,
- };
- return <MessageRow key={message.id} {...props} />;
- })
- }
- </Stack>
+ </Stack>
+ <Waypoint onEnter={() => console.log('should load more messages')} topOffset="-200px" />
+ </Stack>
+ {showScrollButton && (
+ <Box position="relative">
+ <Box position="absolute" bottom="10px" left="50%" sx={{ transform: 'translate(-50%)' }}>
+ <ScrollToEndButton listBottomRef={listBottomRef} />
+ </Box>
+ </Box>
+ )}
+ </>
);
}
+
+interface ScrollToEndButtonProps {
+ listBottomRef: MutableRefObject<HTMLElement | undefined>;
+}
+
+const ScrollToEndButton = ({ listBottomRef }: ScrollToEndButtonProps) => {
+ const { t } = useTranslation();
+ const textColor = 'white';
+ return (
+ <Stack
+ direction="row"
+ borderRadius="5px"
+ height="30px"
+ alignItems="center"
+ padding="0 16px"
+ spacing="12px"
+ sx={{
+ backgroundColor: '#005699', // Should be same color as message bubble
+ cursor: 'pointer',
+ }}
+ onClick={() => listBottomRef.current?.scrollIntoView({ behavior: 'smooth' })}
+ >
+ <ArrowDownIcon sx={{ fontSize: '12px', color: textColor }} />
+ <Typography variant="caption" fontWeight="bold" color={textColor}>
+ {t('messages_scroll_to_end')}
+ </Typography>
+ </Stack>
+ );
+};
diff --git a/client/src/components/SendMessageForm.tsx b/client/src/components/SendMessageForm.tsx
index 7230dd0..f003aae 100644
--- a/client/src/components/SendMessageForm.tsx
+++ b/client/src/components/SendMessageForm.tsx
@@ -55,7 +55,7 @@
);
return (
- <Stack component="form" onSubmit={handleSubmit} direction="row" alignItems="center" spacing="20px">
+ <Stack component="form" onSubmit={handleSubmit} direction="row" alignItems="center" spacing="20px" padding="16px">
<UploadFileButton />
<RecordVoiceMessageButton />
<RecordVideoMessageButton />
diff --git a/client/src/components/SvgIcon.tsx b/client/src/components/SvgIcon.tsx
index 9e560dc..0b8b9ed 100644
--- a/client/src/components/SvgIcon.tsx
+++ b/client/src/components/SvgIcon.tsx
@@ -64,6 +64,17 @@
);
};
+export const ArrowDownIcon = (props: SvgIconProps) => {
+ return (
+ <SvgIcon {...props} viewBox="0 0 8 11.607">
+ <path
+ fillRule="evenodd"
+ d="m4.353 11.43 3.441-3.5.058-.058a.564.564 0 0 0-.058-.816.62.62 0 0 0-.816.058l-2.45 2.508V.583A.551.551 0 0 0 3.945 0a.551.551 0 0 0-.581.583v9.039L.912 7.114a.613.613 0 0 0-.758 0 .621.621 0 0 0 0 .816l3.441 3.5a.452.452 0 0 0 .583.117c.058 0 .117-.058.175-.117"
+ />
+ </SvgIcon>
+ );
+};
+
export const ArrowLeftCurved = (props: SvgIconProps) => {
return (
<SvgIcon {...props} viewBox="0 0 16 8.814">
diff --git a/client/src/locale/en/translation.json b/client/src/locale/en/translation.json
index bef16ed..049bc55 100644
--- a/client/src/locale/en/translation.json
+++ b/client/src/locale/en/translation.json
@@ -26,6 +26,8 @@
"message_input_placeholder_four": "Write to {{member0}}, {{member1}}, {{member2}}, +1 other member",
"message_input_placeholder_more": "Write to {{member0}}, {{member1}}, {{member2}}, +{{excess}} other members",
+ "messages_scroll_to_end": "Scroll to the end of the conversation",
+
"username_input_default_helper_text": "",
"username_input_success_helper_text": "Username available",
"username_input_taken_helper_text": "Username already taken",
diff --git a/client/src/locale/en/translation_old.json b/client/src/locale/en/translation_old.json
new file mode 100644
index 0000000..a1a3e9a
--- /dev/null
+++ b/client/src/locale/en/translation_old.json
@@ -0,0 +1,15 @@
+{
+ "password_input_default_helper_text": "",
+ "password_input_medium_helper_text": "Medium",
+ "password_input_registration_failed_helper_text": "Choose another password!",
+ "password_input_strong_helper_text": "Strong",
+ "password_input_too_weak_helper_text": "Too weak",
+ "password_input_weak_helper_text": "Weak",
+ "severity_error": "Error",
+ "severity_success": "Success",
+ "username_input_default_helper_text": "",
+ "username_input_invalid_helper_text": "Username doesn't follow required pattern",
+ "username_input_registration_failed_helper_text": "Username not correct!",
+ "username_input_success_helper_text": "Username available",
+ "username_input_taken_helper_text": "Username already taken"
+}
diff --git a/client/src/locale/fr/translation.json b/client/src/locale/fr/translation.json
index 97a1cf6..346f0aa 100644
--- a/client/src/locale/fr/translation.json
+++ b/client/src/locale/fr/translation.json
@@ -24,7 +24,9 @@
"message_input_placeholder_two": "Écrire à {{member0}} et {{member1}}",
"message_input_placeholder_three": "Écrire à {{member0}}, {{member1}} et {{member2}}",
"message_input_placeholder_four": "Écrire à {{member0}}, {{member1}}, {{member2}}, +1 autre membre",
- "message_input_placeholder_more": "Écrire à {{member01}}, {{member1}}, {{member2}}, +{{excess}} autres membres",
+ "message_input_placeholder_more": "Écrire à {{member0}}, {{member1}}, {{member2}}, +{{excess}} autres membres",
+
+ "messages_scroll_to_end": "Faire défiler jusqu'à la fin de la conversation",
"share_screen": "Partager votre écran",
"share_screen_area": "Partager une partie de l'écran",
diff --git a/package-lock.json b/package-lock.json
index 37da114..5ab6d9e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -99,6 +99,7 @@
"react-modal": "^3.15.1",
"react-redux": "^8.0.2",
"react-router-dom": "^6.3.0",
+ "react-waypoint": "^10.3.0",
"socket.io-client": "^4.5.2"
},
"devDependencies": {
@@ -6599,6 +6600,11 @@
"resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
"integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ=="
},
+ "node_modules/consolidated-events": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/consolidated-events/-/consolidated-events-2.0.2.tgz",
+ "integrity": "sha512-2/uRVMdRypf5z/TW/ncD/66l75P5hH2vM/GR8Jf8HLc2xnfJtmina6F6du8+v4Z2vTrMo7jC+W1tmEEuuELgkQ=="
+ },
"node_modules/content-disposition": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
@@ -14468,6 +14474,20 @@
"react-dom": ">=16.6.0"
}
},
+ "node_modules/react-waypoint": {
+ "version": "10.3.0",
+ "resolved": "https://registry.npmjs.org/react-waypoint/-/react-waypoint-10.3.0.tgz",
+ "integrity": "sha512-iF1y2c1BsoXuEGz08NoahaLFIGI9gTUAAOKip96HUmylRT6DUtpgoBPjk/Y8dfcFVmfVDvUzWjNXpZyKTOV0SQ==",
+ "dependencies": {
+ "@babel/runtime": "^7.12.5",
+ "consolidated-events": "^1.1.0 || ^2.0.0",
+ "prop-types": "^15.0.0",
+ "react-is": "^17.0.1 || ^18.0.0"
+ },
+ "peerDependencies": {
+ "react": "^15.3.0 || ^16.0.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
"node_modules/read": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz",
@@ -22397,6 +22417,11 @@
"resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
"integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ=="
},
+ "consolidated-events": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/consolidated-events/-/consolidated-events-2.0.2.tgz",
+ "integrity": "sha512-2/uRVMdRypf5z/TW/ncD/66l75P5hH2vM/GR8Jf8HLc2xnfJtmina6F6du8+v4Z2vTrMo7jC+W1tmEEuuELgkQ=="
+ },
"content-disposition": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
@@ -25538,6 +25563,7 @@
"react-modal": "^3.15.1",
"react-redux": "^8.0.2",
"react-router-dom": "^6.3.0",
+ "react-waypoint": "*",
"sass": "^1.54.5",
"socket.io-client": "^4.5.2",
"typescript": "^4.7.4",
@@ -28289,6 +28315,17 @@
"prop-types": "^15.6.2"
}
},
+ "react-waypoint": {
+ "version": "10.3.0",
+ "resolved": "https://registry.npmjs.org/react-waypoint/-/react-waypoint-10.3.0.tgz",
+ "integrity": "sha512-iF1y2c1BsoXuEGz08NoahaLFIGI9gTUAAOKip96HUmylRT6DUtpgoBPjk/Y8dfcFVmfVDvUzWjNXpZyKTOV0SQ==",
+ "requires": {
+ "@babel/runtime": "^7.12.5",
+ "consolidated-events": "^1.1.0 || ^2.0.0",
+ "prop-types": "^15.0.0",
+ "react-is": "^17.0.1 || ^18.0.0"
+ }
+ },
"read": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz",