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 )} ); };