feature/IO-3000-messaging-sockets-migration2 -

- Various work

Signed-off-by: Dave Richer <dave@imexsystems.ca>
This commit is contained in:
Dave Richer
2024-11-28 09:10:23 -08:00
parent ad1761096a
commit 08c0da1bed
6 changed files with 171 additions and 146 deletions

View File

@@ -1,53 +1,85 @@
import React, { useEffect, useRef } from "react";
import React, { useEffect, useRef, useState } from "react";
import { Virtuoso } from "react-virtuoso";
import { renderMessage } from "./renderMessage";
import "./chat-message-list.styles.scss";
const SCROLL_DELAY_MS = 50;
const INITIAL_SCROLL_DELAY_MS = 100;
export default function ChatMessageListComponent({ messages }) {
const virtuosoRef = useRef(null);
const [atBottom, setAtBottom] = useState(true);
const loadedImagesRef = useRef(0);
// Scroll to the bottom after a short delay when the component mounts
const handleScrollStateChange = (isAtBottom) => {
setAtBottom(isAtBottom);
};
const resetImageLoadState = () => {
loadedImagesRef.current = 0;
};
const preloadImages = (imagePaths, onComplete) => {
resetImageLoadState();
if (imagePaths.length === 0) {
onComplete();
return;
}
imagePaths.forEach((url) => {
const img = new Image();
img.src = url;
img.onload = img.onerror = () => {
loadedImagesRef.current += 1;
if (loadedImagesRef.current === imagePaths.length) {
onComplete();
}
};
});
};
// Ensure all images are loaded on initial render
useEffect(() => {
const timer = setTimeout(() => {
if (virtuosoRef?.current?.scrollToIndex && messages?.length) {
const imagePaths = messages
.filter((message) => message.image && message.image_path?.length > 0)
.flatMap((message) => message.image_path);
preloadImages(imagePaths, () => {
if (virtuosoRef.current) {
virtuosoRef.current.scrollToIndex({
index: messages.length - 1,
behavior: "auto" // Instantly scroll to the bottom
align: "end",
behavior: "auto"
});
}
}, INITIAL_SCROLL_DELAY_MS);
});
}, [messages]);
// Cleanup the timeout on unmount
return () => clearTimeout(timer);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []); // ESLint is disabled for this line because we only want this to load once (valid exception)
// Scroll to the bottom after the new messages are rendered
// Handle scrolling when new messages are added
useEffect(() => {
if (virtuosoRef?.current?.scrollToIndex && messages?.length) {
const timeout = setTimeout(() => {
if (!atBottom) return;
const latestMessage = messages[messages.length - 1];
const imagePaths = latestMessage?.image_path || [];
preloadImages(imagePaths, () => {
if (virtuosoRef.current) {
virtuosoRef.current.scrollToIndex({
index: messages.length - 1,
align: "end", // Ensure the last message is fully visible
behavior: "smooth" // Smooth scrolling
align: "end",
behavior: "smooth"
});
}, SCROLL_DELAY_MS); // Slight delay to ensure layout recalculates
// Cleanup timeout on dependency changes
return () => clearTimeout(timeout);
}
}, [messages]); // Triggered when new messages are added
}
});
}, [messages, atBottom]);
return (
<div className="chat">
<Virtuoso
ref={virtuosoRef}
data={messages}
itemContent={(index) => renderMessage(messages, index)} // Pass `messages` to renderMessage
followOutput="smooth" // Ensure smooth scrolling when new data is appended
overscan={!!messages.reduce((acc, message) => acc + (message.image_path?.length || 0), 0) ? messages.length : 0}
itemContent={(index) => renderMessage(messages, index)}
followOutput={(isAtBottom) => handleScrollStateChange(isAtBottom)}
initialTopMostItemIndex={messages.length - 1}
style={{ height: "100%", width: "100%" }}
/>
</div>