import React, {
useCallback,
useEffect,
useLayoutEffect,
useMemo,
useRef,
useState,
} from "react";
import {
Flex,
Icon,
Skeleton,
SkeletonCircle,
Stack,
Text,
} from "@chakra-ui/react";
import { sleep } from "@strata-foundation/spl-utils";
import throttle from "lodash/throttle";
import { useAsyncCallback } from "react-async-hook";
import {
IMessageWithPending,
IMessageWithPendingAndReacts,
} from "../hooks/useMessages";
import { MemodMessage } from "./message/Message";
const INACTIVE_TIME = 60; // After 1 minute, new grouping
const INFINITE_SCROLL_THRESHOLD = 300;
const FETCH_COUNT = 50;
export const ChatMessageSkeleton = () => (
);
const canUseDOM = typeof window !== "undefined";
const useIsomorphicLayoutEffect = canUseDOM ? useLayoutEffect : useEffect;
export const ChatMessages = ({
isLoading,
isLoadingMore,
hasMore,
fetchMore = () => null,
scrollRef,
messages = [],
}: {
isLoading: boolean;
isLoadingMore: boolean;
hasMore: boolean;
fetchMore: (num: number) => void;
scrollRef?: any;
messages?: (IMessageWithPendingAndReacts | IMessageWithPending)[];
}) => {
const myScrollRef = useRef(null);
if (!scrollRef) scrollRef = myScrollRef;
const hasMessages = messages.length > 0;
const [isSticky, setIsSticky] = useState(true);
// On render if we dont have a scroll bar
// and we have hasMore then fetch initialMore
useEffect(() => {
if (
scrollRef.current.scrollHeight == scrollRef.current.offsetHeight &&
hasMore &&
!isLoading
) {
fetchMore(FETCH_COUNT);
}
}, [scrollRef, hasMore, isLoading, fetchMore]);
const handleOnScroll = useCallback(
throttle((e: any) => {
const scrollOffset = e.target.scrollHeight + e.target.scrollTop;
if (
scrollOffset <= e.target.offsetHeight + INFINITE_SCROLL_THRESHOLD &&
!isLoadingMore &&
hasMore
) {
fetchMore(FETCH_COUNT);
}
setIsSticky(e.target.scrollTop > -50);
}, 300),
[isLoadingMore, fetchMore, hasMore, setIsSticky]
);
const loaders = useMemo(() => {
return !messages.length ? (
Array.from(Array(FETCH_COUNT).keys()).map((x, index) => (
))
) : (
);
}, [messages.length]);
const { execute: scrollToMessage } = useAsyncCallback(async function (
id: string
) {
// listen to escape key press to break loop
let breakLoop = false;
function keyPress(e: any) {
if (e.key === "Escape") {
breakLoop = true;
}
}
document.addEventListener("keypress", keyPress);
while (!breakLoop && hasMore && scrollRef.current) {
let findElem = document.getElementById(id as string);
if (findElem) {
findElem.scrollIntoView({ behavior: "smooth", block: "center" });
return;
}
// scroll to the top which should load more messages
scrollRef.current.scroll({
top: -scrollRef.current.scrollHeight,
behavior: "smooth",
});
await sleep(300);
}
document.removeEventListener("keypress", keyPress);
});
const scrollAnchorRef = useRef(null);
useIsomorphicLayoutEffect(() => {
if (isSticky)
scrollAnchorRef.current?.scrollIntoView({
behavior: "smooth",
block: "center",
});
}, [messages, isSticky]);
return (
{!isLoading &&
messages?.map((msg, index) => (
=
(msg.startBlockTime || new Date().valueOf() / 1000) -
INACTIVE_TIME
) || !!(msg as any).reply
}
/>
))}
{(isLoading || isLoadingMore) && loaders}
{!(isLoading || isLoadingMore) && !hasMessages && (
Its Quiet In Here
Say Something
)}
);
};