refactor: load more in msg feed

This commit is contained in:
Tristan Yang
2023-03-09 11:32:44 +08:00
parent 384de7c8df
commit 3af4cab880
8 changed files with 171 additions and 181 deletions
+79 -18
View File
@@ -1,26 +1,43 @@
import { memo, useEffect, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { NavLink, useLocation } from 'react-router-dom';
import { useDebounce } from 'rooks';
import { useDebounce, useLocalstorageState } from 'rooks';
import { ViewportList, ViewportListRef } from 'react-viewport-list';
import { Waveform } from '@uiball/loaders';
import clsx from 'clsx';
import { useReadMessageMutation } from '../../../app/services/message';
import { useLazyLoadMoreMessagesQuery, useReadMessageMutation } from '../../../app/services/message';
import { useAppSelector } from '../../../app/store';
import EditIcon from "../../../assets/icons/edit.svg";
import { renderMessageFragment } from '../utils';
import { KEY_UID } from '../../../app/config';
// import LoadMore from '../LoadMore';
const checkHistory = (key: string) => {
const currUid = localStorage.getItem(KEY_UID) ?? "";
const _key = `${currUid}_${key}`;
const local = Number(localStorage.getItem(_key) ?? 0);
return local == 0;
};
const setHistory = (key: string) => {
const currUid = localStorage.getItem(KEY_UID) ?? "";
const _key = `${currUid}_${key}`;
localStorage.setItem(_key, "1");
};
type Props = {
context: "user" | "channel",
id: number
}
const triggerScrollHeight = 400;
const MessageFeed = ({ context, id }: Props) => {
const { t } = useTranslation("chat");
const [loadMoreMessage, { isLoading: loadingMore, isSuccess, data: historyData }] = useLazyLoadMoreMessagesQuery();
const [historyMid, setHistoryMid] = useLocalstorageState(`history_mid_${context}_${id}`, "");
const listRef = useRef<ViewportListRef | null>(null);
const ref = useRef<HTMLDivElement | null>(
null,
);
const { t } = useTranslation("chat");
const { pathname } = useLocation();
const [updateReadIndex] = useReadMessageMutation();
const updateReadDebounced = useDebounce(updateReadIndex, 300);
@@ -41,21 +58,49 @@ const MessageFeed = ({ context, id }: Props) => {
messageData: store.message || {}
};
});
const debouncedScrollHandler = useDebounce(() => {
const container = ref ? ref.current : null;
if (container && historyMid && !loadingMore) {
if (container.scrollTop <= 200) {
// pull up
console.log("start pul up");
loadMoreMessage({ context, id, mid: +historyMid });
}
}
}, 500);
useEffect(() => {
if (isSuccess && historyData) {
if (historyData.length == 0) {
// 到顶了
setHistory(`history_${context}_${id}`);
} else {
// 记录最新的mid
const [{ mid }] = historyData;
setHistoryMid(String(mid));
}
}
}, [isSuccess, historyData, context, id]);
useEffect(() => {
// context changed, scroll to bottom
console.log("listRef", listRef);
if (listRef && listRef.current) {
const list = listRef.current;
ref.current?.classList.remove("scroll-smooth");
list.scrollToIndex({
prerender: 40,
index: Number.POSITIVE_INFINITY
});
setTimeout(() => {
// tricky
ref.current?.classList.add("scroll-smooth");
}, 150);
}
const hasHistory = checkHistory(`history_${context}_${id}`);
const container = ref ? ref.current : null;
if (hasHistory && container) {
container.addEventListener("scroll", debouncedScrollHandler);
}
return () => {
if (hasHistory && container) {
container.removeEventListener("scroll", debouncedScrollHandler);
}
};
}, [context, id]);
useEffect(() => {
// check current scroll position first, scroll to bottom only when under the trigger number
@@ -64,22 +109,35 @@ const MessageFeed = ({ context, id }: Props) => {
const { scrollHeight, scrollTop, offsetHeight } = container;
const deltaNum = scrollHeight - scrollTop - offsetHeight;
console.log("delta", deltaNum);
// sent by myself (tricky!)
const [lastMid] = mids ? mids.slice(-1) : [0];
const ts = new Date().getTime();
const isSentByMyself = ts - lastMid < 1000;
if (deltaNum < triggerScrollHeight) {
container.classList.add("scroll-smooth");
// scroll to bottom
container.scrollTop = container.scrollHeight;
container.classList.remove("scroll-smooth");
} else if (isSentByMyself) {
container.scrollTop = container.scrollHeight;
}
}
}, [mids]);
// 首次进入,就用mids
const lastMid = mids ? mids.slice(0, 1)[0] : "";
if (!historyMid && lastMid) {
setHistoryMid(`${lastMid}`);
}
}, [mids, historyMid]);
const handleMessageListChange = (data: [number, number]) => {
const [first, last] = data;
console.log("index changed", data);
};
const readIndex = context == "channel" ? footprint.readChannels[id] : Number.POSITIVE_INFINITY;
// const handleMessageListChange = (data: [number, number]) => {
// const [first, last] = data;
// // todo
// // console.log("index changed", data);
// };
const readIndex = context == "channel" ? footprint.readChannels[id] : footprint.readUsers[id];
const isEmptyList = !mids || mids.length == 0;
return (
<article ref={ref} id={`VOCECHAT_FEED_${context}_${id}`} className="w-full h-full px-1 md:px-4 py-4.5 overflow-x-hidden overflow-y-scroll scroll-smooth will-change-contents">
<article ref={ref} id={`VOCECHAT_FEED_${context}_${id}`} className="w-full h-full px-1 md:px-4 py-4.5 overflow-x-hidden overflow-y-scroll will-change-contents">
{context == "channel" && <div className="pt-14 px-1 md:px-0 flex flex-col items-start gap-2">
<h2 className="font-bold text-4xl dark:text-white">{t("welcome_channel", { name: data?.name })}</h2>
<p className="text-gray-600 dark:text-gray-300">{t("welcome_desc", { name: data?.name })} </p>
@@ -90,8 +148,11 @@ const MessageFeed = ({ context, id }: Props) => {
</NavLink>
)}
</div>}
<div className={clsx("mt-2 w-full py-2", loadingMore ? "flex-center" : "hidden")} >
<Waveform size={18} lineWeight={4} speed={1} color="#ccc" />
</div>
{isEmptyList ? null : <ViewportList
onViewportIndexesChange={handleMessageListChange}
// onViewportIndexesChange={handleMessageListChange}
overscan={10}
// itemSize={100}
initialPrerender={40}