Files
colanode/desktop/src/components/messages/message-list.tsx

118 lines
3.8 KiB
TypeScript
Raw Normal View History

2024-08-03 19:14:38 +02:00
import React from 'react';
import { ScrollArea } from '@/components/ui/scroll-area';
import { InView } from 'react-intersection-observer';
import { Message } from '@/components/messages/message';
2024-08-22 20:17:36 +02:00
import { MessageNode } from '@/types/messages';
2024-08-03 19:14:38 +02:00
interface MessageListProps {
conversationId: string;
2024-08-22 20:17:36 +02:00
messages: MessageNode[];
2024-08-03 19:14:38 +02:00
}
2024-08-22 20:17:36 +02:00
export const MessageList = ({ conversationId, messages }: MessageListProps) => {
2024-08-03 19:14:38 +02:00
const viewportRef = React.useRef<HTMLDivElement>(null);
const containerRef = React.useRef<HTMLDivElement>(null);
const observerRef = React.useRef<ResizeObserver | null>(null);
const scrollPosition = React.useRef<number>(0);
const bottomRef = React.useRef<HTMLDivElement>(null);
const bottomVisibleRef = React.useRef<boolean>(false);
const shouldScrollToBottom = React.useRef<boolean>(true);
React.useEffect(() => {
if (bottomRef.current && scrollPosition.current == 0) {
bottomRef.current.scrollIntoView();
}
if (containerRef.current && viewportRef.current) {
// observe resize of container when new messages are appended or internal elements are loaded (e.g. images)
observerRef.current = new ResizeObserver(() => {
if (!viewportRef.current) {
return;
}
if (shouldScrollToBottom) {
bottomRef.current?.scrollIntoView();
} else {
viewportRef.current.scrollTop =
viewportRef.current.scrollHeight - scrollPosition.current;
}
});
observerRef.current.observe(containerRef.current);
return () => {
if (observerRef.current) {
observerRef.current.disconnect();
}
};
}
}, [conversationId]);
2024-08-03 19:14:38 +02:00
const handleScroll = () => {
if (viewportRef.current) {
scrollPosition.current =
viewportRef.current.scrollHeight - viewportRef.current.scrollTop;
shouldScrollToBottom.current = bottomVisibleRef.current;
}
};
return (
<ScrollArea
ref={viewportRef}
onScroll={handleScroll}
className="flex-grow overflow-y-auto px-10"
>
<InView
rootMargin="200px"
onChange={(inView) => {
// if (inView && hasPrevious && !isLoadingPrevious) {
// loadPrevious(50);
// }
}}
>
{/*<div className="flex w-full items-center justify-center py-4">*/}
{/* {isLoadingPrevious && <Loader />}*/}
{/*</div>*/}
</InView>
2024-08-29 19:40:36 +02:00
<div className="container" ref={containerRef}>
2024-08-03 19:14:38 +02:00
{messages.map((message, index) => {
const previousMessage = index > 0 ? messages[index - 1] : null;
const currentMessageDate = new Date(message.createdAt);
const previousMessageDate = previousMessage
? new Date(previousMessage.createdAt)
: null;
const showDate =
!previousMessageDate ||
currentMessageDate.getDate() !== previousMessageDate.getDate();
return (
<React.Fragment key={message.id}>
{showDate && (
<div className="relative flex items-center py-1">
<div className="flex-grow border-t border-gray-100" />
<span className="mx-4 flex-shrink text-xs text-muted-foreground">
{currentMessageDate.toDateString()}
</span>
<div className="flex-grow border-t border-gray-100" />
</div>
)}
2024-08-22 20:17:36 +02:00
<Message message={message} previousMessage={previousMessage} />
2024-08-03 19:14:38 +02:00
</React.Fragment>
);
})}
</div>
<InView
className="h-4"
rootMargin="20px"
onChange={(inView) => {
bottomVisibleRef.current = inView;
}}
>
<div ref={bottomRef} className="h-4"></div>
</InView>
</ScrollArea>
);
};