@pubuduth-aplicy/chat-ui 2.1.39 → 2.1.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/components/Loader.tsx +17 -0
- package/src/components/messages/Messages.tsx +162 -26
- package/src/style/style.css +107 -1
package/package.json
CHANGED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
const Loader = () => {
|
|
2
|
+
return (
|
|
3
|
+
|
|
4
|
+
<div className="dot-spinner">
|
|
5
|
+
<div className="dot-spinner__dot" />
|
|
6
|
+
<div className="dot-spinner__dot" />
|
|
7
|
+
<div className="dot-spinner__dot" />
|
|
8
|
+
<div className="dot-spinner__dot" />
|
|
9
|
+
<div className="dot-spinner__dot" />
|
|
10
|
+
<div className="dot-spinner__dot" />
|
|
11
|
+
<div className="dot-spinner__dot" />
|
|
12
|
+
<div className="dot-spinner__dot" />
|
|
13
|
+
</div>
|
|
14
|
+
)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export default Loader
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
-
import { useCallback, useEffect, useRef } from "react";
|
|
2
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
3
3
|
import Message from "./Message";
|
|
4
4
|
import { useChatContext } from "../../providers/ChatProvider";
|
|
5
5
|
import { useMessages } from "../../hooks/queries/useChatApi";
|
|
@@ -7,27 +7,144 @@ import useChatUIStore from "../../stores/Zustant";
|
|
|
7
7
|
import { useInfiniteQuery } from "@tanstack/react-query";
|
|
8
8
|
import { fetchMessages } from "../../service/messageService";
|
|
9
9
|
import { useInView } from "react-intersection-observer";
|
|
10
|
+
import Loader from "../Loader";
|
|
10
11
|
|
|
11
12
|
const Messages = () => {
|
|
12
13
|
const { selectedConversation, setMessages, messages } = useChatUIStore();
|
|
13
14
|
const { userId, socket } = useChatContext();
|
|
14
15
|
const { ref, inView } = useInView();
|
|
15
16
|
const scrollContainerRef = useRef<HTMLDivElement>(null);
|
|
16
|
-
|
|
17
|
+
const [isInitialLoad, setIsInitialLoad] = useState(true);
|
|
18
|
+
const [isAutoScrolling, setIsAutoScrolling] = useState(false);
|
|
19
|
+
const [prevMessagesLength, setPrevMessagesLength] = useState(0);
|
|
20
|
+
const loadingRef = useRef(false);
|
|
17
21
|
// const { data, isLoading, isError, error } = useMessages(selectedConversation?._id, userId);
|
|
18
22
|
|
|
19
23
|
const lastMessageRef = useRef<HTMLDivElement>(null);
|
|
20
24
|
|
|
21
25
|
const { data, fetchNextPage, hasNextPage, isFetchingNextPage } =
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
26
|
+
useInfiniteQuery({
|
|
27
|
+
queryKey: ["messages", selectedConversation?._id, userId],
|
|
28
|
+
queryFn: ({ pageParam = 1 }) =>
|
|
29
|
+
fetchMessages(selectedConversation?._id, userId, pageParam),
|
|
30
|
+
getNextPageParam: (lastPage) => {
|
|
31
|
+
return lastPage.nextPage; // Use the nextPage from API response
|
|
32
|
+
},
|
|
33
|
+
initialPageParam: 1,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const [isLoadingMore, setIsLoadingMore] = useState(false);
|
|
37
|
+
const scrollHeightBeforeLoad = useRef(0);
|
|
38
|
+
|
|
39
|
+
const handleScroll = useCallback(() => {
|
|
40
|
+
if (!scrollContainerRef.current || isFetchingNextPage || !hasNextPage) return;
|
|
41
|
+
|
|
42
|
+
const { scrollTop, scrollHeight, clientHeight } = scrollContainerRef.current;
|
|
43
|
+
|
|
44
|
+
// Load more when scrolled near the top (100px threshold)
|
|
45
|
+
if (scrollTop < 100) {
|
|
46
|
+
// Save current scroll height before loading
|
|
47
|
+
scrollHeightBeforeLoad.current = scrollHeight;
|
|
48
|
+
setIsLoadingMore(true);
|
|
49
|
+
fetchNextPage().finally(() => setIsLoadingMore(false));
|
|
50
|
+
}
|
|
51
|
+
}, [fetchNextPage, isFetchingNextPage, hasNextPage]);
|
|
52
|
+
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
if (isLoadingMore && scrollContainerRef.current && scrollHeightBeforeLoad.current) {
|
|
55
|
+
// After new messages are loaded, adjust scroll position to maintain the same view
|
|
56
|
+
const container = scrollContainerRef.current;
|
|
57
|
+
const newScrollHeight = container.scrollHeight;
|
|
58
|
+
container.scrollTop = newScrollHeight - scrollHeightBeforeLoad.current;
|
|
59
|
+
}
|
|
60
|
+
}, [messages.length, isLoadingMore]);
|
|
61
|
+
|
|
62
|
+
// Scroll to bottom for new messages
|
|
63
|
+
useEffect(() => {
|
|
64
|
+
if (messages.length > 0 && !isLoadingMore) {
|
|
65
|
+
setTimeout(() => {
|
|
66
|
+
lastMessageRef.current?.scrollIntoView({ behavior: "smooth" });
|
|
67
|
+
}, 100);
|
|
68
|
+
}
|
|
69
|
+
}, [messages.length, isLoadingMore]);
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
// const {
|
|
73
|
+
// data,
|
|
74
|
+
// fetchNextPage,
|
|
75
|
+
// hasNextPage,
|
|
76
|
+
// isFetchingNextPage,
|
|
77
|
+
// isLoading,
|
|
78
|
+
// } = useInfiniteQuery({
|
|
79
|
+
// queryKey: ["messages", selectedConversation?._id, userId],
|
|
80
|
+
// queryFn: ({ pageParam = 1 }) =>
|
|
81
|
+
// fetchMessages(selectedConversation?._id, userId, pageParam),
|
|
82
|
+
// getNextPageParam: (lastPage) => lastPage.nextPage ?? undefined,
|
|
83
|
+
// onSuccess: (data) => {
|
|
84
|
+
// const allMessages = data.pages.flatMap(page => page.messages);
|
|
85
|
+
// setMessages(allMessages);
|
|
86
|
+
|
|
87
|
+
// // Scroll to bottom on initial load
|
|
88
|
+
// if (isInitialLoad && scrollContainerRef.current) {
|
|
89
|
+
// setTimeout(() => {
|
|
90
|
+
// scrollContainerRef.current?.scrollTo({
|
|
91
|
+
// top: scrollContainerRef.current.scrollHeight,
|
|
92
|
+
// behavior: 'auto'
|
|
93
|
+
// });
|
|
94
|
+
// setIsInitialLoad(false);
|
|
95
|
+
// }, 100);
|
|
96
|
+
// }
|
|
97
|
+
// },
|
|
98
|
+
// });
|
|
99
|
+
|
|
100
|
+
// Maintain scroll position when loading older messages
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
// useEffect(() => {
|
|
104
|
+
// console.log('scrollContainerRef',scrollContainerRef.current);
|
|
105
|
+
|
|
106
|
+
// if (!scrollContainerRef.current || isInitialLoad) return;
|
|
107
|
+
|
|
108
|
+
// if (isFetchingNextPage) {
|
|
109
|
+
// // Store current scroll position before loading
|
|
110
|
+
// const container = scrollContainerRef.current;
|
|
111
|
+
// const scrollTopBefore = container.scrollTop;
|
|
112
|
+
// const scrollHeightBefore = container.scrollHeight;
|
|
113
|
+
// console.log(container,'scrollHeightBefore',scrollHeightBefore,'container',scrollTopBefore);
|
|
114
|
+
|
|
115
|
+
// loadingRef.current = true;
|
|
116
|
+
|
|
117
|
+
// return () => {
|
|
118
|
+
// // After messages are loaded, adjust scroll position
|
|
119
|
+
// requestAnimationFrame(() => {
|
|
120
|
+
// container.scrollTop = container.scrollHeight - scrollHeightBefore + scrollTopBefore;
|
|
121
|
+
// loadingRef.current = false;
|
|
122
|
+
// });
|
|
123
|
+
// };
|
|
124
|
+
// }
|
|
125
|
+
// }, [isFetchingNextPage, isInitialLoad]);
|
|
126
|
+
|
|
127
|
+
// Add this useEffect to handle new messages
|
|
128
|
+
// useEffect(() => {
|
|
129
|
+
// if (!scrollContainerRef.current || loadingRef.current) return;
|
|
130
|
+
|
|
131
|
+
// const container = scrollContainerRef.current;
|
|
132
|
+
// console.log("container",container);
|
|
133
|
+
|
|
134
|
+
// const isNearBottom = container.scrollHeight - container.scrollTop - container.clientHeight < 100;
|
|
135
|
+
|
|
136
|
+
// // Only auto-scroll if we're near bottom or it's a new message
|
|
137
|
+
// if (isNearBottom || messages.length > prevMessagesLength) {
|
|
138
|
+
// setIsAutoScrolling(true);
|
|
139
|
+
// container.scrollTo({
|
|
140
|
+
// top: container.scrollHeight,
|
|
141
|
+
// behavior: messages.length > prevMessagesLength ? 'smooth' : 'auto'
|
|
142
|
+
// });
|
|
143
|
+
// setTimeout(() => setIsAutoScrolling(false), 500);
|
|
144
|
+
// }
|
|
145
|
+
|
|
146
|
+
// setPrevMessagesLength(messages.length);
|
|
147
|
+
// }, [messages.length]);
|
|
31
148
|
|
|
32
149
|
useEffect(() => {
|
|
33
150
|
if (inView) {
|
|
@@ -45,7 +162,9 @@ const Messages = () => {
|
|
|
45
162
|
|
|
46
163
|
useEffect(() => {
|
|
47
164
|
if (data) {
|
|
48
|
-
|
|
165
|
+
console.log('message fetching data',data);
|
|
166
|
+
|
|
167
|
+
const allMessages = data.pages.flatMap(page => page.messages).reverse();
|
|
49
168
|
setMessages(allMessages);
|
|
50
169
|
}
|
|
51
170
|
}, [data]);
|
|
@@ -122,15 +241,23 @@ useEffect(() => {
|
|
|
122
241
|
return () => observer.disconnect();
|
|
123
242
|
}, [messages, socket]);
|
|
124
243
|
|
|
125
|
-
const handleScroll = useCallback(() => {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
}, [fetchNextPage, isFetchingNextPage, hasNextPage]);
|
|
244
|
+
// const handleScroll = useCallback(() => {
|
|
245
|
+
// if (!scrollContainerRef.current || isFetchingNextPage || !hasNextPage) return;
|
|
246
|
+
|
|
247
|
+
// const { scrollTop } = scrollContainerRef.current;
|
|
248
|
+
// // Load more when scrolled near the top (10% threshold)
|
|
249
|
+
// if (scrollTop < 100) {
|
|
250
|
+
// fetchNextPage();
|
|
251
|
+
// }
|
|
252
|
+
// }, [fetchNextPage, isFetchingNextPage, hasNextPage]);
|
|
253
|
+
|
|
254
|
+
// useEffect(() => {
|
|
255
|
+
// const container = scrollContainerRef.current;
|
|
256
|
+
// if (container) {
|
|
257
|
+
// container.addEventListener('scroll', handleScroll);
|
|
258
|
+
// return () => container.removeEventListener('scroll', handleScroll);
|
|
259
|
+
// }
|
|
260
|
+
// }, [handleScroll]);
|
|
134
261
|
|
|
135
262
|
// useEffect(() => {
|
|
136
263
|
// const scrollContainer = scrollContainerRef.current;
|
|
@@ -139,20 +266,29 @@ useEffect(() => {
|
|
|
139
266
|
// return () => scrollContainer.removeEventListener("scroll", handleScroll);
|
|
140
267
|
// }
|
|
141
268
|
// }, [handleScroll]);
|
|
142
|
-
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
console.log("📩 Messages:", messages);
|
|
143
272
|
console.log("📩 Messages Length:", messages?.length);
|
|
144
273
|
|
|
274
|
+
|
|
275
|
+
|
|
145
276
|
return (
|
|
146
|
-
<div className="chatMessages"
|
|
147
|
-
|
|
277
|
+
<div className="chatMessages"
|
|
278
|
+
ref={scrollContainerRef}
|
|
279
|
+
style={{ overflowY: 'auto', height: '100%', position: 'relative' }}
|
|
280
|
+
>
|
|
281
|
+
|
|
148
282
|
<div ref={ref} className="my-8">
|
|
149
|
-
{isFetchingNextPage ?
|
|
283
|
+
{isFetchingNextPage ? <Loader /> : null}
|
|
150
284
|
</div>
|
|
151
285
|
{messages?.length > 0 ? (
|
|
152
286
|
messages?.map((message: any) =>
|
|
153
287
|
// Check if the message object is valid and has an _id before rendering
|
|
154
288
|
message ? (
|
|
155
|
-
<div key={message._id} ref={lastMessageRef}
|
|
289
|
+
<div key={message._id} ref={lastMessageRef}
|
|
290
|
+
style={{ flex: 1, minHeight: 0, overflowY: 'auto' }}
|
|
291
|
+
>
|
|
156
292
|
<Message message={message} />
|
|
157
293
|
</div>
|
|
158
294
|
) : null
|
package/src/style/style.css
CHANGED
|
@@ -442,4 +442,110 @@
|
|
|
442
442
|
50% {
|
|
443
443
|
transform: translateY(-3px);
|
|
444
444
|
}
|
|
445
|
-
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/* From Uiverse.io by abrahamcalsin */
|
|
448
|
+
.dot-spinner {
|
|
449
|
+
--uib-size: 2.8rem;
|
|
450
|
+
--uib-speed: .9s;
|
|
451
|
+
--uib-color: #183153;
|
|
452
|
+
position: relative;
|
|
453
|
+
display: flex;
|
|
454
|
+
align-items: center;
|
|
455
|
+
justify-content: flex-start;
|
|
456
|
+
height: var(--uib-size);
|
|
457
|
+
width: var(--uib-size);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
.dot-spinner__dot {
|
|
461
|
+
position: absolute;
|
|
462
|
+
top: 0;
|
|
463
|
+
left: 0;
|
|
464
|
+
display: flex;
|
|
465
|
+
align-items: center;
|
|
466
|
+
justify-content: flex-start;
|
|
467
|
+
height: 100%;
|
|
468
|
+
width: 100%;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
.dot-spinner__dot::before {
|
|
472
|
+
content: '';
|
|
473
|
+
height: 20%;
|
|
474
|
+
width: 20%;
|
|
475
|
+
border-radius: 50%;
|
|
476
|
+
background-color: var(--uib-color);
|
|
477
|
+
transform: scale(0);
|
|
478
|
+
opacity: 0.5;
|
|
479
|
+
animation: pulse0112 calc(var(--uib-speed) * 1.111) ease-in-out infinite;
|
|
480
|
+
box-shadow: 0 0 20px rgba(18, 31, 53, 0.3);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
.dot-spinner__dot:nth-child(2) {
|
|
484
|
+
transform: rotate(45deg);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
.dot-spinner__dot:nth-child(2)::before {
|
|
488
|
+
animation-delay: calc(var(--uib-speed) * -0.875);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
.dot-spinner__dot:nth-child(3) {
|
|
492
|
+
transform: rotate(90deg);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
.dot-spinner__dot:nth-child(3)::before {
|
|
496
|
+
animation-delay: calc(var(--uib-speed) * -0.75);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
.dot-spinner__dot:nth-child(4) {
|
|
500
|
+
transform: rotate(135deg);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
.dot-spinner__dot:nth-child(4)::before {
|
|
504
|
+
animation-delay: calc(var(--uib-speed) * -0.625);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
.dot-spinner__dot:nth-child(5) {
|
|
508
|
+
transform: rotate(180deg);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
.dot-spinner__dot:nth-child(5)::before {
|
|
512
|
+
animation-delay: calc(var(--uib-speed) * -0.5);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
.dot-spinner__dot:nth-child(6) {
|
|
516
|
+
transform: rotate(225deg);
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
.dot-spinner__dot:nth-child(6)::before {
|
|
520
|
+
animation-delay: calc(var(--uib-speed) * -0.375);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
.dot-spinner__dot:nth-child(7) {
|
|
524
|
+
transform: rotate(270deg);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
.dot-spinner__dot:nth-child(7)::before {
|
|
528
|
+
animation-delay: calc(var(--uib-speed) * -0.25);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
.dot-spinner__dot:nth-child(8) {
|
|
532
|
+
transform: rotate(315deg);
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
.dot-spinner__dot:nth-child(8)::before {
|
|
536
|
+
animation-delay: calc(var(--uib-speed) * -0.125);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
@keyframes pulse0112 {
|
|
540
|
+
0%,
|
|
541
|
+
100% {
|
|
542
|
+
transform: scale(0);
|
|
543
|
+
opacity: 0.5;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
50% {
|
|
547
|
+
transform: scale(1);
|
|
548
|
+
opacity: 1;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
|