88 lines
2.4 KiB
JavaScript
88 lines
2.4 KiB
JavaScript
import { useCallback, useEffect, useRef, useState } from "react";
|
|
import { Virtuoso } from "react-virtuoso";
|
|
import { renderMessage } from "./renderMessage";
|
|
import "./chat-message-list.styles.scss";
|
|
|
|
export default function ChatMessageListComponent({ messages }) {
|
|
const virtuosoRef = useRef(null);
|
|
const [atBottom, setAtBottom] = useState(true);
|
|
const loadedImagesRef = useRef(0);
|
|
|
|
const handleScrollStateChange = (isAtBottom) => {
|
|
setAtBottom(isAtBottom);
|
|
};
|
|
|
|
const resetImageLoadState = () => {
|
|
loadedImagesRef.current = 0;
|
|
};
|
|
|
|
const preloadImages = useCallback((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 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,
|
|
align: "end",
|
|
behavior: "auto"
|
|
});
|
|
}
|
|
});
|
|
}, [messages, preloadImages]);
|
|
|
|
// Handle scrolling when new messages are added
|
|
useEffect(() => {
|
|
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",
|
|
behavior: "smooth"
|
|
});
|
|
}
|
|
});
|
|
}, [messages, atBottom, preloadImages]);
|
|
|
|
return (
|
|
<div className="chat">
|
|
<Virtuoso
|
|
ref={virtuosoRef}
|
|
data={messages}
|
|
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>
|
|
);
|
|
}
|