From 1f6bfa16292835c2e6d33c9568167da9a961cf15 Mon Sep 17 00:00:00 2001 From: zerosoul Date: Tue, 24 May 2022 21:40:18 +0800 Subject: [PATCH] feat: pagination message feed --- package.json | 1 + src/common/hook/useMessageFeed.js | 114 +++++++++++++++++++++ src/routes/chat/ChannelChat/LoadMore.js | 51 ++++++++++ src/routes/chat/ChannelChat/index.js | 128 +++++++++++++----------- src/routes/chat/ChannelChat/styled.js | 13 ++- yarn.lock | 9 +- 6 files changed, 254 insertions(+), 62 deletions(-) create mode 100644 src/common/hook/useMessageFeed.js create mode 100644 src/routes/chat/ChannelChat/LoadMore.js diff --git a/package.json b/package.json index 9c7e77ef..dfcdd0a7 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ "react-helmet": "^6.1.0", "react-hot-toast": "^2.2.0", "react-icons": "^4.3.1", + "react-infinite-scroller": "^1.2.6", "react-linkify": "^1.0.0-alpha", "react-pdf": "^5.7.2", "react-redux": "^8.0.2", diff --git a/src/common/hook/useMessageFeed.js b/src/common/hook/useMessageFeed.js new file mode 100644 index 00000000..c03bc576 --- /dev/null +++ b/src/common/hook/useMessageFeed.js @@ -0,0 +1,114 @@ +import { useEffect, useState, useRef } from "react"; +import { useSelector } from "react-redux"; +const getFeedWithPagination = (config) => { + const { pageNumber = 1, pageSize = 10, mids = [], isLast = false } = + config || {}; + const shadowMids = mids.slice(0); + + if (shadowMids.length == 0) + return { + pageCount: 0, + pageSize, + pageNumber: 1, + ids: [], + }; + shadowMids.sort((a, b) => { + return Number(a) - Number(b); + }); + console.log("message pagination", shadowMids); + const pageCount = Math.ceil(shadowMids.length / pageSize); + const computedPageNumber = isLast ? pageCount : pageNumber; + const ids = shadowMids.slice( + (computedPageNumber - 1) * pageSize, + computedPageNumber * pageSize + ); + return { + pageCount, + pageSize, + pageNumber: computedPageNumber, + ids, + }; +}; +export default function useMessageFeed({ context = "channel", id = null }) { + const listRef = useRef([]); + const pageRef = useRef(null); + const [hasMore, setHasMore] = useState(true); + const [appends, setAppends] = useState([]); + const [items, setItems] = useState([]); + const mids = useSelector((store) => { + return context == "channel" + ? store.channelMessage[id] || [] + : store.userMessage.byId[id] || []; + }); + useEffect(() => { + listRef.current = []; + pageRef.current = []; + setItems([]); + setHasMore(true); + setAppends([]); + }, [context, id]); + + useEffect(() => { + if (listRef.current.length == 0) { + // 初次 + const pageInfo = getFeedWithPagination({ mids, isLast: true }); + console.log("pull up 2", pageInfo); + pageRef.current = pageInfo; + listRef.current = pageInfo.ids; + setItems(listRef.current); + console.log("message pageInfo", mids, pageInfo); + } else { + // 追加 + const lastMid = listRef.current.slice(-1); + const sorteds = mids.slice(0).sort((a, b) => { + return Number(a) - Number(b); + }); + const appends = sorteds.filter((s) => s > lastMid); + if (appends.length) { + setAppends(appends); + } + } + }, [mids]); + const pullUp = () => { + const currPageInfo = pageRef.current; + console.log("pull up", currPageInfo); + // 第一页 + if (currPageInfo && currPageInfo.pageNumber == 1) { + setHasMore(false); + return; + } + let pageInfo = null; + if (!currPageInfo) { + // 初始化 + pageInfo = getFeedWithPagination({ + mids, + isLast: true, + }); + } else { + const prevPageNumber = currPageInfo.pageNumber - 1; + pageInfo = getFeedWithPagination({ + mids, + pageNumber: prevPageNumber, + }); + } + pageRef.current = pageInfo; + listRef.current = [...pageInfo.ids, ...listRef.current]; + setTimeout(() => { + setItems(listRef.current); + console.log("pull up", currPageInfo, listRef.current); + setHasMore(pageInfo.pageNumber !== 1); + }, 300); + }; + const pullDown = () => { + // 向下加载 + }; + + return { + mids, + appends, + hasMore, + pullUp, + pullDown, + list: items, + }; +} diff --git a/src/routes/chat/ChannelChat/LoadMore.js b/src/routes/chat/ChannelChat/LoadMore.js new file mode 100644 index 00000000..049a14ec --- /dev/null +++ b/src/routes/chat/ChannelChat/LoadMore.js @@ -0,0 +1,51 @@ +// import { useRef, useEffect } from "react"; +import styled from "styled-components"; +import { Waveform } from "@uiball/loaders"; + +const Styled = styled.div` + display: flex; + justify-content: center; + align-items: center; + width: 100%; + padding: 8px; + /* background-color: #eee; */ +`; +export default function LoadMore() { + // const ref = useRef(undefined); + // useEffect(() => { + // const observer = new IntersectionObserver( + // (entries) => { + // entries.forEach((entry) => { + // const intersecting = entry.isIntersecting; + // // const currEle = entry.target; + // if (intersecting && loadMore) { + // // load more + // console.log("inview"); + // loadMore(); + // } + // }); + // }, + // { threshold: 0 } + // ); + // const currEle = ref?.current; + // if (currEle) { + // observer.observe(ref.current); + // } + // return () => { + // if (currEle) { + // observer.unobserve(currEle); + // } + // }; + // }, [ref]); + return ( + + + + ); +} diff --git a/src/routes/chat/ChannelChat/index.js b/src/routes/chat/ChannelChat/index.js index c3afd5ae..2f505f48 100644 --- a/src/routes/chat/ChannelChat/index.js +++ b/src/routes/chat/ChannelChat/index.js @@ -2,12 +2,15 @@ import { useState, useEffect } from "react"; import { useDebounce } from "rooks"; import { NavLink, useLocation } from "react-router-dom"; import Tippy from "@tippyjs/react"; +import InfiniteScroll from "react-infinite-scroller"; import { useDispatch, useSelector } from "react-redux"; import PinList from "./PinList"; +import LoadMore from "./LoadMore"; import FavList from "../FavList"; import { useReadMessageMutation } from "../../../app/services/message"; import { updateRemeberedNavs } from "../../../app/slices/ui"; -import useChatScroll from "../../../common/hook/useChatScroll"; +// import useChatScroll from "../../../common/hook/useChatScroll"; +import useMessageFeed from "../../../common/hook/useMessageFeed"; import ChannelIcon from "../../../common/component/ChannelIcon"; import Tooltip from "../../../common/component/Tooltip"; import Contact from "../../../common/component/Contact"; @@ -23,15 +26,14 @@ import IconHeadphone from "../../../assets/icons/headphone.svg"; import boardosIcon from "../../../assets/icons/app.boardos.svg?url"; import webrowseIcon from "../../../assets/icons/app.webrowse.svg?url"; import addIcon from "../../../assets/icons/add.svg?url"; -import { - // StyledNotification, - StyledContacts, - StyledChannelChat, - StyledHeader, -} from "./styled"; +import { StyledContacts, StyledChannelChat, StyledHeader } from "./styled"; import InviteModal from "../../../common/component/InviteModal"; export default function ChannelChat({ cid = "", dropFiles = [] }) { + const { list: msgIdList, appends, hasMore, pullUp } = useMessageFeed({ + context: "channel", + id: cid, + }); const [toolVisible, setToolVisible] = useState(""); const { pathname } = useLocation(); const dispatch = useDispatch(); @@ -41,7 +43,6 @@ export default function ChannelChat({ cid = "", dropFiles = [] }) { const [addMemberModalVisible, setAddMemberModalVisible] = useState(false); const { selects, - msgIds, userIds, data, messageData, @@ -54,16 +55,16 @@ export default function ChannelChat({ cid = "", dropFiles = [] }) { footprint: store.footprint, loginUser: store.contacts.byId[store.authData.uid], loginUid: store.authData.uid, - msgIds: store.channelMessage[cid] || [], userIds: store.contacts.ids, data: store.channels.byId[cid] || {}, messageData: store.message || {}, }; }); - const ref = useChatScroll(msgIds); + // const ref = useChatScroll(mids); // const handleClearUnreads = () => { // dispatch(readMessage(msgIds)); // }; + // remember the route while switching out useEffect(() => { dispatch(updateRemeberedNavs()); return () => { @@ -83,9 +84,9 @@ export default function ChannelChat({ cid = "", dropFiles = [] }) { ? userIds : members.slice(0).sort((n) => (n == owner ? -1 : 0)); const addVisible = loginUser?.is_admin || owner == loginUid; - console.log("channel message list", msgIds); const readIndex = footprint.readChannels[cid]; const pinCount = data?.pinned_messages?.length || 0; + const finalHasMore = selects ? false : hasMore; return ( <> {addMemberModalVisible && ( @@ -99,21 +100,11 @@ export default function ChannelChat({ cid = "", dropFiles = [] }) { aside={ <>