From 9b379e95612bd6b3f28734a0aaa57d1e0ecf5650 Mon Sep 17 00:00:00 2001 From: Tristan Yang Date: Tue, 7 Mar 2023 11:04:38 +0800 Subject: [PATCH] refactor: message feed --- src/routes/chat/ChannelChat/Members.tsx | 7 +- src/routes/chat/ChannelChat/index.tsx | 64 +---------------- src/routes/chat/DMChat/index.tsx | 45 +----------- src/routes/chat/Layout/MessageFeed.tsx | 95 +++++++++++++++++++++++++ src/routes/chat/Layout/index.tsx | 7 +- src/routes/chat/SessionList/index.tsx | 60 ++++++++++------ src/routes/chat/index.tsx | 16 ++--- 7 files changed, 149 insertions(+), 145 deletions(-) create mode 100644 src/routes/chat/Layout/MessageFeed.tsx diff --git a/src/routes/chat/ChannelChat/Members.tsx b/src/routes/chat/ChannelChat/Members.tsx index 83cfd2da..d7179b51 100644 --- a/src/routes/chat/ChannelChat/Members.tsx +++ b/src/routes/chat/ChannelChat/Members.tsx @@ -1,8 +1,8 @@ import { useRef, useState } from 'react'; import { ViewportList } from 'react-viewport-list'; +import { useTranslation } from 'react-i18next'; import User from "../../../common/component/User"; import IconAdd from "../../../assets/icons/add.svg"; -import { useTranslation } from 'react-i18next'; import InviteModal from "../../../common/component/InviteModal"; @@ -16,9 +16,7 @@ type Props = { const Members = ({ uids, addVisible, ownerId, cid, membersVisible }: Props) => { const { t } = useTranslation("chat"); - const ref = useRef( - null, - ); + const ref = useRef(null); const [addMemberModalVisible, setAddMemberModalVisible] = useState(false); const toggleAddVisible = () => { setAddMemberModalVisible((prev) => !prev); @@ -34,6 +32,7 @@ const Members = ({ uids, addVisible, ownerId, cid, membersVisible }: Props) => { )} diff --git a/src/routes/chat/ChannelChat/index.tsx b/src/routes/chat/ChannelChat/index.tsx index 618e5961..7d143ce9 100644 --- a/src/routes/chat/ChannelChat/index.tsx +++ b/src/routes/chat/ChannelChat/index.tsx @@ -1,24 +1,17 @@ import { useState, useEffect, memo } from "react"; -import { useDebounce } from "rooks"; -import { NavLink, useLocation } from "react-router-dom"; +import { useLocation } from "react-router-dom"; import Tippy from "@tippyjs/react"; import { useDispatch } from "react-redux"; import PinList from "./PinList"; import FavList from "../FavList"; -import { useReadMessageMutation } from "../../../app/services/message"; import { updateRememberedNavs } from "../../../app/slices/ui"; -import useMessageFeed from "../../../common/hook/useMessageFeed"; import ChannelIcon from "../../../common/component/ChannelIcon"; import Tooltip from "../../../common/component/Tooltip"; import Layout from "../Layout"; -import { renderMessageFragment } from "../utils"; -import EditIcon from "../../../assets/icons/edit.svg"; import IconFav from "../../../assets/icons/bookmark.svg"; import IconPeople from "../../../assets/icons/people.svg"; import IconPin from "../../../assets/icons/pin.svg"; - -import LoadMore from "../LoadMore"; import { useAppSelector } from "../../../app/store"; import { useTranslation } from "react-i18next"; import GoBackNav from "../../../common/component/GoBackNav"; @@ -29,37 +22,19 @@ type Props = { }; function ChannelChat({ cid = 0, dropFiles = [] }: Props) { const { t } = useTranslation("chat"); - const { - pulling, - list: msgIds, - appends, - hasMore, - pullUp - } = useMessageFeed({ - context: "channel", - id: cid - }); const { pathname } = useLocation(); const dispatch = useDispatch(); - const [updateReadIndex] = useReadMessageMutation(); - const updateReadDebounced = useDebounce(updateReadIndex, 300); const [membersVisible, setMembersVisible] = useState(true); const { - selects, userIds, data, - messageData, loginUser, - footprint } = useAppSelector((store) => { return { - selects: store.ui.selectMessages[`channel_${cid}`], - footprint: store.footprint, loginUser: store.authData.user, userIds: store.users.ids, data: store.channels.byId[cid], - messageData: store.message || {} }; }); useEffect(() => { @@ -77,9 +52,7 @@ function ChannelChat({ cid = 0, dropFiles = [] }: Props) { 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 == loginUser?.uid; - const readIndex = footprint.readChannels[cid]; const pinCount = data?.pinned_messages?.length || 0; - const feeds = [...msgIds, ...appends]; const toolClass = `relative cursor-pointer`; return ( <> @@ -142,40 +115,7 @@ function ChannelChat({ cid = 0, dropFiles = [] }: Props) { users={ } - > - <> - {hasMore ? ( - - ) : ( -
-

{t("welcome_channel", { name })}

-

{t("welcome_desc", { name })}

- {loginUser?.is_admin && ( - - - {t("edit_channel")} - - )} -
- )} - {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 == loginUser?.uid || mid <= readIndex; - return renderMessageFragment({ - selectMode: !!selects, - updateReadIndex: updateReadDebounced, - read, - prev, - curr, - contextId: cid, - context: "channel" - }); - })} - - + /> ); } diff --git a/src/routes/chat/DMChat/index.tsx b/src/routes/chat/DMChat/index.tsx index 2a9c659d..7bde1997 100644 --- a/src/routes/chat/DMChat/index.tsx +++ b/src/routes/chat/DMChat/index.tsx @@ -1,15 +1,10 @@ import { FC } from "react"; -import { useDebounce } from "rooks"; import Tippy from "@tippyjs/react"; import FavList from "../FavList"; import Tooltip from "../../../common/component/Tooltip"; import FavIcon from "../../../assets/icons/bookmark.svg"; -import { useReadMessageMutation } from "../../../app/services/message"; import User from "../../../common/component/User"; import Layout from "../Layout"; -import LoadMore from "../LoadMore"; -import { renderMessageFragment } from "../utils"; -import useMessageFeed from "../../../common/hook/useMessageFeed"; import { useAppSelector } from "../../../app/store"; import GoBackNav from "../../../common/component/GoBackNav"; type Props = { @@ -17,30 +12,12 @@ type Props = { dropFiles?: File[]; }; const DMChat: FC = ({ uid = 0, dropFiles }) => { - const { - pulling, - list: msgIds, - appends, - hasMore, - pullUp - } = useMessageFeed({ - context: "user", - id: uid - }); - const [updateReadIndex] = useReadMessageMutation(); - const updateReadDebounced = useDebounce(updateReadIndex, 300); - const { currUser, messageData, footprint, loginUid, selects } = useAppSelector((store) => { + const { currUser } = useAppSelector((store) => { return { - selects: store.ui.selectMessages[`user_${uid}`], - loginUid: store.authData.user?.uid, - footprint: store.footprint, currUser: store.users.byId[uid], - messageData: store.message }; }); if (!currUser) return null; - const readIndex = footprint.readUsers[uid]; - const feeds = [...msgIds, ...appends]; return ( = ({ uid = 0, dropFiles }) => { } - > - <> - {hasMore ? : null} - {[...feeds].map((mid, idx) => { - const curr = messageData[mid]; - const prev = idx == 0 ? null : messageData[feeds[idx - 1]]; - const read = curr?.from_uid == loginUid || mid <= readIndex; - return renderMessageFragment({ - selectMode: !!selects, - updateReadIndex: updateReadDebounced, - read, - prev, - curr, - contextId: uid, - context: "user" - }); - })} - - + /> ); }; export default DMChat; diff --git a/src/routes/chat/Layout/MessageFeed.tsx b/src/routes/chat/Layout/MessageFeed.tsx new file mode 100644 index 00000000..05d9366d --- /dev/null +++ b/src/routes/chat/Layout/MessageFeed.tsx @@ -0,0 +1,95 @@ +import { useEffect, useRef } from 'react'; +import { useTranslation } from 'react-i18next'; +import { NavLink, useLocation } from 'react-router-dom'; +import { useDebounce } from 'rooks'; +import { ViewportList } from 'react-viewport-list'; + +import { useReadMessageMutation } from '../../../app/services/message'; +import { useAppSelector } from '../../../app/store'; +import EditIcon from "../../../assets/icons/edit.svg"; +import { renderMessageFragment } from '../utils'; + + +type Props = { + context: "user" | "channel", + id: number +} + +const MessageFeed = ({ context, id }: Props) => { + // const listRef = useRef(null); + const ref = useRef( + null, + ); + const { t } = useTranslation("chat"); + const { pathname } = useLocation(); + const [updateReadIndex] = useReadMessageMutation(); + const updateReadDebounced = useDebounce(updateReadIndex, 300); + const { + mids, + selects, + data, + messageData, + loginUser, + footprint + } = useAppSelector((store) => { + return { + mids: context == "user" ? store.userMessage.byId[id] : store.channelMessage[id], + selects: store.ui.selectMessages[`${context}_${id}`], + footprint: store.footprint, + loginUser: store.authData.user, + data: context == "channel" ? store.channels.byId[id] : undefined, + messageData: store.message || {} + }; + }); + // useEffect(() => { + // // 滚动到记忆位置 + // if (listRef && listRef.current) { + // listRef.current.scrollToIndex({ index: 5 }); + // } + // }, [context, id]); + + const readIndex = context == "channel" ? footprint.readChannels[id] : Number.POSITIVE_INFINITY; + const isEmptyList = !mids || mids.length == 0; + return ( +
+ {context == "channel" &&
+

{t("welcome_channel", { name: data?.name })}

+

{t("welcome_desc", { name: data?.name })}

+ {loginUser?.is_admin && ( + + + {t("edit_channel")} + + )} +
} + {isEmptyList ? null : + {((mid, idx) => { + const curr = messageData[mid]; + if (!curr) return null; + const isFirst = idx == 0; + const prev = isFirst ? null : messageData[mids[idx - 1]]; + const read = curr?.from_uid == loginUser?.uid || mid <= readIndex; + return renderMessageFragment({ + selectMode: !!selects, + updateReadIndex: updateReadDebounced, + read, + prev, + curr, + contextId: id, + context + }); + })} + } +
+ ); +}; + +export default MessageFeed; \ No newline at end of file diff --git a/src/routes/chat/Layout/index.tsx b/src/routes/chat/Layout/index.tsx index 66f4bc9f..27f9efa8 100644 --- a/src/routes/chat/Layout/index.tsx +++ b/src/routes/chat/Layout/index.tsx @@ -15,10 +15,10 @@ import LicenseUpgradeTip from "./LicenseOutdatedTip"; import DnDTip from "./DnDTip"; import IconWarning from '../../../assets/icons/warning.svg'; import ImagePreview from "../../../common/component/ImagePreview"; +import MessageFeed from "./MessageFeed"; interface Props { readonly?: boolean; - children: ReactElement; header: ReactElement; aside?: ReactElement | null; users?: ReactElement | null; @@ -29,7 +29,6 @@ interface Props { const Layout: FC = ({ readonly = false, - children, header, aside = null, users = null, @@ -97,9 +96,7 @@ const Layout: FC = ({
{/* 消息流 */} -
- {children} -
+ {/* 发送框 */}
{readonly ? ( diff --git a/src/routes/chat/SessionList/index.tsx b/src/routes/chat/SessionList/index.tsx index bb7e953b..bc0d56eb 100644 --- a/src/routes/chat/SessionList/index.tsx +++ b/src/routes/chat/SessionList/index.tsx @@ -1,20 +1,21 @@ -import { FC } from "react"; +import { FC, useRef } from "react"; import { useState, useEffect } from "react"; +import { ViewportList } from "react-viewport-list"; import Session from "./Session"; import DeleteChannelConfirmModal from "../../settingChannel/DeleteConfirmModal"; import InviteModal from "../../../common/component/InviteModal"; import { useAppSelector } from "../../../app/store"; export interface ChatSession { - key: string; type: "user" | "channel"; id: number; - mid?: number; - unread?: number; + mid: number; + unread: number; } type Props = { tempSession?: ChatSession; }; const SessionList: FC = ({ tempSession }) => { + const ref = useRef(null); const [deleteId, setDeleteId] = useState(); const [inviteChannelId, setInviteChannelId] = useState(); const [sessions, setSessions] = useState([]); @@ -35,27 +36,31 @@ const SessionList: FC = ({ tempSession }) => { const cSessions = channelIDs.map((id) => { const mids = channelMessage[id]; if (!mids || mids.length == 0) { - return { key: `channel_${id}`, unreads: 0, id, type: "channel" }; + return { unreads: 0, id, type: "channel" }; } // 先转换成数字,再排序 const mid = [...mids].sort((a, b) => +a - +b).pop(); - return { key: `channel_${id}`, id, mid, type: "channel" }; + return { id, mid, type: "channel" }; }); const uSessions = DMs.map((id) => { + console.log("adddd", id); const mids = userMessage[id]; if (!mids || mids.length == 0) { - return { key: `user_${id}`, unreads: 0, id, type: "user" }; + return { unreads: 0, id, type: "user" }; } // 先转换成数字,再排序 const mid = [...mids].sort((a, b) => +a - +b).pop(); - return { key: `user_${id}`, type: "user", id, mid }; + return { type: "user", id, mid }; }); const temps = [...(cSessions as ChatSession[]), ...(uSessions as ChatSession[])].sort((a, b) => { const { mid: aMid = 0 } = a; const { mid: bMid = 0 } = b; return bMid - aMid; }); - setSessions(tempSession ? [tempSession, ...temps] : temps); + console.log("before qqqq", temps); + const newSessions = tempSession ? [tempSession, ...temps] : temps; + console.log("qqqq", newSessions); + setSessions(newSessions); }, [ channelIDs, DMs, @@ -66,22 +71,31 @@ const SessionList: FC = ({ tempSession }) => { userMessage, tempSession ]); + return ( <> -
    - {sessions.map((s) => { - const { key, type, id, mid = 0 } = s; - return ( - - ); - })} +
      + + {(s) => { + console.log("ssss", sessions); + const { type, id, mid = 0 } = s; + const key = `${type}_${id}`; + return ( + + ); + }} +
    {!!deleteId && ( { setSessionListVisible(prev => !prev); }; - const tmpSession = - sessionUids.findIndex((i) => i == user_id) > -1 - ? undefined - : { - key: `user_${user_id}`, - unreads: 0, + const tmpSession = user_id == 0 ? undefined : + sessionUids.findIndex((i) => i == +user_id) == -1 + ? { + mid: 0, + unread: 0, id: +user_id, - type: "user" as "user" | "channel" - }; + type: "user" as const + } + : undefined; // console.log("temp uid", tmpUid); const placeholderVisible = channel_id == 0 && user_id == 0; const isMainPath = isHomePath || isChatHomePath;