refactor: message feed pagination

This commit is contained in:
zerosoul
2022-06-05 13:02:35 +08:00
parent 13a4c7c302
commit c73d531fa5
9 changed files with 415 additions and 121 deletions
+4 -4
View File
@@ -11,7 +11,7 @@ const Styled = styled.div`
padding: 30px 0;
/* background-color: #eee; */
`;
export default function LoadMore({ pullDown = null }) {
export default function LoadMore({ pullUp = null }) {
const ref = useRef(undefined);
useEffect(() => {
const observer = new IntersectionObserver(
@@ -19,10 +19,10 @@ export default function LoadMore({ pullDown = null }) {
entries.forEach((entry) => {
const intersecting = entry.isIntersecting;
// const currEle = entry.target;
if (intersecting && pullDown) {
if (intersecting && pullUp) {
// load more
console.log("inview");
pullDown();
pullUp();
}
});
},
@@ -37,7 +37,7 @@ export default function LoadMore({ pullDown = null }) {
observer.unobserve(currEle);
}
};
}, [ref, pullDown]);
}, [ref, pullUp]);
return (
<Styled ref={ref}>
<Waveform
+23 -24
View File
@@ -3,12 +3,10 @@ import { useDebounce } from "rooks";
import { NavLink, useLocation } from "react-router-dom";
import Tippy from "@tippyjs/react";
import { useDispatch, useSelector } from "react-redux";
// import InfiniteScroll from "react-infinite-scroll-component";
import PinList from "./PinList";
import FavList from "../FavList";
import { useReadMessageMutation } from "../../../app/services/message";
import { updateRemeberedNavs } from "../../../app/slices/ui";
// import useChatScroll from "../../../common/hook/useChatScroll";
import useMessageFeed from "../../../common/hook/useMessageFeed";
import ChannelIcon from "../../../common/component/ChannelIcon";
@@ -36,7 +34,7 @@ import InviteModal from "../../../common/component/InviteModal";
import LoadMore from "./LoadMore";
export default function ChannelChat({ cid = "", dropFiles = [] }) {
const { list: msgIds, prepends, hasMore, pullDown } = useMessageFeed({
const { list: msgIds, appends, hasMore, pullUp } = useMessageFeed({
context: "channel",
id: cid,
});
@@ -94,7 +92,7 @@ export default function ChannelChat({ cid = "", dropFiles = [] }) {
console.log("channel message list", msgIds);
const readIndex = footprint.readChannels[cid];
const pinCount = data?.pinned_messages?.length || 0;
const feeds = [...prepends, ...msgIds];
const feeds = [...msgIds, ...appends];
return (
<>
{addMemberModalVisible && (
@@ -233,27 +231,9 @@ export default function ChannelChat({ cid = "", dropFiles = [] }) {
}
>
<StyledChannelChat id={`RUSTCHAT_FEED_channel_${cid}`}>
{/* <div className="feed"> */}
{feeds.map((mid, idx) => {
const curr = messageData[mid];
if (!curr) return null;
const isFirst = idx == 0;
const prev =
idx == feeds.length - 1 ? null : messageData[feeds[idx + 1]];
const read = curr?.from_uid == loginUid || mid <= readIndex;
return renderMessageFragment({
selectMode: !!selects,
updateReadIndex: updateReadDebounced,
read,
isFirst,
prev,
curr,
contextId: cid,
context: "channel",
});
})}
{/* <div className="anchor"></div> */}
{hasMore ? (
<LoadMore pullDown={pullDown} />
<LoadMore pullUp={pullUp} />
) : (
<div className="info">
<h2 className="title">Welcome to #{name} !</h2>
@@ -267,6 +247,25 @@ export default function ChannelChat({ cid = "", dropFiles = [] }) {
</NavLink>
</div>
)}
{/* <div className="feed"> */}
{feeds.map((mid, idx) => {
const curr = messageData[mid];
if (!curr) return null;
const isFirst = idx == 0;
const prev = isFirst ? null : messageData[feeds[idx - 1]];
const read = curr?.from_uid == loginUid || mid <= readIndex;
return renderMessageFragment({
selectMode: !!selects,
updateReadIndex: updateReadDebounced,
read,
isFirst,
prev,
curr,
contextId: cid,
context: "channel",
});
})}
{/* </div> */}
</StyledChannelChat>
{/* {unreads != 0 && (
@@ -0,0 +1,288 @@
import { useState, useEffect } from "react";
import { useDebounce } from "rooks";
import { NavLink, useLocation } from "react-router-dom";
import Tippy from "@tippyjs/react";
import { useDispatch, useSelector } from "react-redux";
// import InfiniteScroll from "react-infinite-scroll-component";
import PinList from "./PinList";
import FavList from "../FavList";
import { useReadMessageMutation } from "../../../app/services/message";
import { updateRemeberedNavs } from "../../../app/slices/ui";
// 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";
import Layout from "../Layout";
import { renderMessageFragment } from "../utils";
import EditIcon from "../../../assets/icons/edit.svg";
// import alertIcon from "../../../assets/icons/alert.svg?url";
import IconFav from "../../../assets/icons/bookmark.svg";
import IconPeople from "../../../assets/icons/people.svg";
import IconPin from "../../../assets/icons/pin.svg";
// import searchIcon from "../../../assets/icons/search.svg?url";
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 InviteModal from "../../../common/component/InviteModal";
import LoadMore from "./LoadMore";
export default function ChannelChat({ cid = "", dropFiles = [] }) {
const { list: msgIds, prepends, hasMore, pullDown } = useMessageFeed({
context: "channel",
id: cid,
});
const [toolVisible, setToolVisible] = useState("");
const { pathname } = useLocation();
const dispatch = useDispatch();
const [updateReadIndex] = useReadMessageMutation();
const updateReadDebounced = useDebounce(updateReadIndex, 300);
const [membersVisible, setMembersVisible] = useState(true);
const [addMemberModalVisible, setAddMemberModalVisible] = useState(false);
const {
selects,
// msgIds,
userIds,
data,
messageData,
loginUid,
loginUser,
footprint,
} = useSelector((store) => {
return {
selects: store.ui.selectMessages[`channel_${cid}`],
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 handleClearUnreads = () => {
// dispatch(readMessage(msgIds));
// };
useEffect(() => {
dispatch(updateRemeberedNavs());
return () => {
dispatch(updateRemeberedNavs({ path: pathname }));
};
}, [pathname]);
const toggleMembersVisible = () => {
setMembersVisible((prev) => !prev);
};
const toggleAddVisible = () => {
setAddMemberModalVisible((prev) => !prev);
};
if (!data) return null;
const { name, description, is_public, members = [], owner } = data;
const memberIds = is_public
? 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 feeds = [...prepends, ...msgIds];
return (
<>
{addMemberModalVisible && (
<InviteModal cid={cid} closeModal={toggleAddVisible} />
)}
<Layout
to={cid}
context="channel"
dropFiles={dropFiles}
// ref={containerRef}
aside={
<>
<ul className="tools">
<li className="tool">
<Tooltip tip="Voice/Video Chat" placement="left">
<IconHeadphone />
</Tooltip>
</li>
<Tooltip
tip="Pin"
placement="left"
disabled={toolVisible == "pin"}
>
<Tippy
onShow={() => {
setToolVisible("pin");
}}
onHide={() => {
setToolVisible("");
}}
placement="left-start"
popperOptions={{ strategy: "fixed" }}
offset={[0, 80]}
interactive
trigger="click"
content={<PinList id={cid} />}
>
<li
className={`tool ${pinCount > 0 ? "badge" : ""} ${
toolVisible == "pin" ? "active" : ""
} `}
data-count={pinCount}
>
<IconPin />
</li>
</Tippy>
</Tooltip>
<Tooltip
tip="Favorite"
placement="left"
disabled={toolVisible == "favorite"}
>
<Tippy
onShow={() => {
setToolVisible("favorite");
}}
onHide={() => {
setToolVisible("");
}}
placement="left-start"
popperOptions={{ strategy: "fixed" }}
offset={[0, 180]}
interactive
trigger="click"
content={<FavList cid={cid} />}
>
<li
className={`tool fav ${
toolVisible == "favorite" ? "active" : ""
} `}
data-count={pinCount}
>
<IconFav />
</li>
</Tippy>
</Tooltip>
<li
className={`tool ${membersVisible ? "active" : ""}`}
onClick={toggleMembersVisible}
>
<Tooltip tip="Channel Members" placement="left">
<IconPeople />
</Tooltip>
</li>
</ul>
<hr className="divider" />
<ul className="apps">
<li className="app">
<Tooltip tip="Webrowse" placement="left">
<img src={webrowseIcon} alt="app icon" />
</Tooltip>
</li>
<li className="app">
<Tooltip tip="BoardOS" placement="left">
<img src={boardosIcon} alt="app icon" />
</Tooltip>
</li>
</ul>
</>
}
header={
<StyledHeader className="head">
<div className="txt">
<ChannelIcon personal={!is_public} />
<span className="title">{name}</span>
<span className="desc">{description}</span>
</div>
</StyledHeader>
}
contacts={
membersVisible ? (
<>
<StyledContacts>
{addVisible && (
<div className="add" onClick={toggleAddVisible}>
<img className="icon" src={addIcon} />
<div className="txt">Add members</div>
</div>
)}
{memberIds.map((uid) => {
return (
<Contact
enableContextMenu={true}
cid={cid}
owner={owner == uid}
key={uid}
uid={uid}
dm
popover
/>
);
})}
</StyledContacts>
</>
) : null
}
>
<StyledChannelChat id={`RUSTCHAT_FEED_channel_${cid}`}>
{/* <div className="feed"> */}
{feeds.map((mid, idx) => {
const curr = messageData[mid];
if (!curr) return null;
const isFirst = idx == 0;
const prev =
idx == feeds.length - 1 ? null : messageData[feeds[idx + 1]];
const read = curr?.from_uid == loginUid || mid <= readIndex;
return renderMessageFragment({
selectMode: !!selects,
updateReadIndex: updateReadDebounced,
read,
isFirst,
prev,
curr,
contextId: cid,
context: "channel",
});
})}
{hasMore ? (
<LoadMore pullDown={pullDown} />
) : (
<div className="info">
<h2 className="title">Welcome to #{name} !</h2>
<p className="desc">This is the start of the #{name} channel. </p>
<NavLink
to={`/setting/channel/${cid}?f=${pathname}`}
className="edit"
>
<EditIcon className="icon" />
Edit Channel
</NavLink>
</div>
)}
{/* </div> */}
</StyledChannelChat>
{/* {unreads != 0 && (
<StyledNotification>
<div className="content">
{unreads} new messages
{msgs.lastAccess
? `since ${dayjs(msgs.lastAccess).format("YYYY-MM-DD h:mm:ss A")}`
: ""}
</div>
<button onClick={handleClearUnreads} className="clear">
Mark As Read
</button>
</StyledNotification>
)} */}
</Layout>
</>
);
}
+3 -2
View File
@@ -88,14 +88,15 @@ export const StyledChannelChat = styled.article`
height: -webkit-fill-available;
overflow-x: hidden;
overflow-y: auto;
/* overflow-anchor: none; */
/* pagination start */
transform: rotate(180deg);
/* transform: rotate(180deg);
direction: rtl;
> div,
> hr {
direction: ltr;
transform: rotate(180deg);
}
} */
/* pagination end */
> .info {
padding-top: 62px;
+1 -1
View File
@@ -182,6 +182,7 @@ export const renderMessageFragment = ({
console.log("_key", _key, local_id, mid);
return (
<React.Fragment key={_key}>
{divider && <Divider content={divider}></Divider>}
<MessageWrapper
key={_key}
data-key={_key}
@@ -201,7 +202,6 @@ export const renderMessageFragment = ({
contextId={contextId}
/>
</MessageWrapper>
{divider && <Divider content={divider}></Divider>}
</React.Fragment>
);
// React.memo(