From b60160cd503d896116d9fad7a79f86978bd94510 Mon Sep 17 00:00:00 2001 From: zerosoul Date: Sun, 20 Mar 2022 17:41:09 +0800 Subject: [PATCH] feat: read index --- .../listener.middleware/handler.channels.js | 2 +- .../listener.middleware/handler.contacts.js | 2 +- src/app/services/channel.js | 3 +- src/app/services/contact.js | 3 +- src/app/services/message.js | 38 ++++++----- src/app/slices/contacts.js | 5 +- src/common/component/Contact.js | 6 +- src/common/component/Message/index.js | 68 ++++++++++--------- .../component/Send/UploadModal/index.js | 54 +++++++-------- src/routes/chat/ChannelList/NavItem.js | 22 +++--- src/routes/chat/DMChat/index.js | 7 +- src/routes/chat/DMList.js | 31 +++++++-- src/routes/chat/index.js | 21 ++---- src/routes/chat/utils.js | 47 ++++++++----- src/routes/contacts/styled.js | 2 + src/routes/home/usePreload.js | 6 +- 16 files changed, 179 insertions(+), 138 deletions(-) diff --git a/src/app/listener.middleware/handler.channels.js b/src/app/listener.middleware/handler.channels.js index 986961c6..e076b707 100644 --- a/src/app/listener.middleware/handler.channels.js +++ b/src/app/listener.middleware/handler.channels.js @@ -11,7 +11,7 @@ export default async function handler({ operation, data, payload }) { const chs = payload; await Promise.all( chs.map(({ gid, ...rest }) => { - return table.setItem(gid, { gid, ...rest }); + return table.setItem(gid + "", { gid, ...rest }); }) ); } diff --git a/src/app/listener.middleware/handler.contacts.js b/src/app/listener.middleware/handler.contacts.js index 5a5d83d3..773777ad 100644 --- a/src/app/listener.middleware/handler.contacts.js +++ b/src/app/listener.middleware/handler.contacts.js @@ -11,7 +11,7 @@ export default async function handler({ operation, data, payload }) { const contacts = payload; await Promise.all( contacts.map(({ uid, ...rest }) => { - return table.setItem(uid, { uid, ...rest }); + return table.setItem(uid + "", { uid, ...rest }); }) ); } diff --git a/src/app/services/channel.js b/src/app/services/channel.js index 0db7cf87..7908a89c 100644 --- a/src/app/services/channel.js +++ b/src/app/services/channel.js @@ -75,9 +75,10 @@ export const channelApi = createApi({ }, }), sendChannelMsg: builder.mutation({ - query: ({ id, content, type = "text" }) => ({ + query: ({ id, content, type = "text", properties = "" }) => ({ headers: { "content-type": ContentTypes[type], + "X-Properties": properties, }, url: `group/${id}/send`, method: "POST", diff --git a/src/app/services/contact.js b/src/app/services/contact.js index 5eb1e494..9cb67d9b 100644 --- a/src/app/services/contact.js +++ b/src/app/services/contact.js @@ -73,9 +73,10 @@ export const contactApi = createApi({ }), }), sendMsg: builder.mutation({ - query: ({ id, content, type = "text" }) => ({ + query: ({ id, content, type = "text", properties = "" }) => ({ headers: { "content-type": ContentTypes[type], + "X-Properties": properties, }, url: `user/${id}/send`, method: "POST", diff --git a/src/app/services/message.js b/src/app/services/message.js index 283d0265..5be3505a 100644 --- a/src/app/services/message.js +++ b/src/app/services/message.js @@ -1,5 +1,5 @@ import { createApi } from "@reduxjs/toolkit/query/react"; -import { batch } from "react-redux"; +// import { batch } from "react-redux"; import { ContentTypes } from "../config"; import { updateReadChannels, updateReadUsers } from "../slices/footprint"; @@ -57,27 +57,31 @@ export const messageApi = createApi({ method: "POST", body: data, }), - async onQueryStarted(data, { dispatch, getState, queryFulfilled }) { - const { users = [], groups = [] } = data; - const { readUsers, readChannels } = getState().footprint.readUsers; - const prevUsers = users.map(({ uid }) => { - return { uid, mid: readUsers[uid] }; - }); - const prevChannels = users.map(({ gid }) => { - return { gid, mid: readChannels[gid] }; - }); - batch(() => { - dispatch(updateReadChannels(groups)); + async onQueryStarted(data, { dispatch, queryFulfilled }) { + const { users = null, groups = null } = data; + // const { readUsers, readChannels } = getState().footprint; + // const prevUsers = users.map(({ uid }) => { + // return { uid, mid: readUsers[uid] }; + // }); + // const prevChannels = users.map(({ gid }) => { + // return { gid, mid: readChannels[gid] }; + // }); + // batch(() => { + if (users) { dispatch(updateReadUsers(users)); - }); + } + if (groups) { + dispatch(updateReadChannels(groups)); + } + // }); try { await queryFulfilled; } catch { // todo - batch(() => { - dispatch(updateReadChannels(prevChannels)); - dispatch(updateReadUsers(prevUsers)); - }); + // batch(() => { + // dispatch(updateReadChannels(prevChannels)); + // dispatch(updateReadUsers(prevUsers)); + // }); } }, }), diff --git a/src/app/slices/contacts.js b/src/app/slices/contacts.js index 1fb1c92a..d3d85562 100644 --- a/src/app/slices/contacts.js +++ b/src/app/slices/contacts.js @@ -50,7 +50,10 @@ const contactsSlice = createSlice({ case "create": { state.byId[uid] = { uid, ...rest }; - state.ids.push(uid); + const idx = state.ids.findIndex((i) => i == uid); + if (idx == -1) { + state.ids.push(uid); + } } break; case "delete": diff --git a/src/common/component/Contact.js b/src/common/component/Contact.js index 191afa1a..08cfe83d 100644 --- a/src/common/component/Contact.js +++ b/src/common/component/Contact.js @@ -83,10 +83,8 @@ export default function Contact({ content={} >
- -
+ +
{!compact && {curr?.name}} diff --git a/src/common/component/Message/index.js b/src/common/component/Message/index.js index ec498203..c9b15ce6 100644 --- a/src/common/component/Message/index.js +++ b/src/common/component/Message/index.js @@ -1,36 +1,41 @@ import React, { useRef, useState, useEffect } from "react"; import dayjs from "dayjs"; -import { useSelector, useDispatch } from "react-redux"; -import { useInViewRef } from "rooks"; +import { useSelector } from "react-redux"; +import { useInViewRef, useDebounce } from "rooks"; import Tippy from "@tippyjs/react"; import Reaction from "./Reaction"; import Reply from "./Reply"; import Profile from "../Profile"; import Avatar from "../Avatar"; -import { readMessage } from "../../../app/slices/message"; import { useReadMessageMutation } from "../../../app/services/message"; import StyledWrapper from "./styled"; import Commands from "./Commands"; import EditMessage from "./EditMessage"; import renderContent from "./renderContent"; -function Message({ contextId = 0, mid = "", read = true, context = "user" }) { +function Message({ contextId = 0, mid = "", context = "user" }) { const [updateReadIndex] = useReadMessageMutation(); + const updateReadDebounced = useDebounce(updateReadIndex, 300); const [myRef, inView] = useInViewRef(); const [edit, setEdit] = useState(false); const [emojiPopVisible, setEmojiPopVisible] = useState(false); const [menuVisible, setMenuVisible] = useState(false); - const disptach = useDispatch(); const avatarRef = useRef(null); - const { message = {}, reactionMessageData, contactsData } = useSelector( - (store) => { - return { - reactionMessageData: store.reactionMessage, - message: store.message[mid], - contactsData: store.contacts.byId, - }; - } - ); + const { + footprint, + message = {}, + reactionMessageData, + contactsData, + loginUid, + } = useSelector((store) => { + return { + footprint: store.footprint, + loginUid: store.authData.uid, + reactionMessageData: store.reactionMessage, + message: store.message[mid] || {}, + contactsData: store.contacts.byId, + }; + }); const toggleMenu = () => { setMenuVisible((prev) => !prev); }; @@ -40,38 +45,35 @@ function Message({ contextId = 0, mid = "", read = true, context = "user" }) { const toggleEmojiPopover = () => { setEmojiPopVisible((prev) => !prev); }; - // useEffect(() => { - // if (!read) { - // avatarRef.current?.scrollIntoView(); - // } - // }, [read]); - // console.log("message", mid, messageData[mid]); - - useEffect(() => { - if (inView && !read) { - disptach(readMessage(mid)); - const data = - context == "user" - ? { users: [{ uid: +contextId, mid }] } - : { groups: [{ gid: +contextId, mid }] }; - updateReadIndex(data); - } - }, [mid, read, inView]); - if (!message) return null; const { reply_mid, from_uid: fromUid, created_at: time, - sending, + sending = false, content, thumbnail, content_type = "text/plain", edited, properties, } = message; + const readIndex = + context == "user" + ? footprint.readUsers[contextId] + : footprint.readChannels[contextId]; + useEffect(() => { + const read = fromUid == loginUid || mid <= readIndex; + if (inView && !read) { + const data = + context == "user" + ? { users: [{ uid: +contextId, mid }] } + : { groups: [{ gid: +contextId, mid }] }; + updateReadDebounced(data); + } + }, [mid, readIndex, inView, fromUid, loginUid]); const reactions = reactionMessageData[mid]; const currUser = contactsData[fromUid] || {}; + // if (!message) return null; return ( { - // files.forEach((file, idx) => { - // const { name, size, type } = file; - // setProperties((prevs) => { - // prevs[idx] = { name, size, type }; - // return prevs; - // }); - // var fileReader = new FileReader(); - // fileReader.onloadend = (e) => { - // let dataUrl = e.target.result; - // let tmp = new Image(); - // tmp.src = dataUrl; - // tmp.onload = function () { - // console.log("image load", this.width, this.height); - // setProperties((prevs) => { - // prevs[idx].width = this.width; - // prevs[idx].height = this.height; - // return prevs; - // }); - // }; - // }; - // fileReader.readAsDataURL(file); - // }); - // }, [files]); + const [properties, setProperties] = useState([]); + useEffect(() => { + files.forEach((file, idx) => { + const { name, size, type } = file; + setProperties((prevs) => { + prevs[idx] = { name, size, type }; + return prevs; + }); + // var fileReader = new FileReader(); + // fileReader.onloadend = (e) => { + // let dataUrl = e.target.result; + // let tmp = new Image(); + // tmp.src = dataUrl; + // tmp.onload = function () { + // console.log("image load", this.width, this.height); + // setProperties((prevs) => { + // prevs[idx].width = this.width; + // prevs[idx].height = this.height; + // return prevs; + // }); + // }; + // }; + // fileReader.readAsDataURL(file); + }); + }, [files]); const handleUpload = () => { const uploadFn = type == "user" ? sendUserMsg : sendChannelMsg; uploadFn({ id: sendTo, content: files[0], - // properties: properties[0], + properties: btoa(JSON.stringify(properties[0])), type: "image", from_uid, }); diff --git a/src/routes/chat/ChannelList/NavItem.js b/src/routes/chat/ChannelList/NavItem.js index e0ee0bd0..412b4cdb 100644 --- a/src/routes/chat/ChannelList/NavItem.js +++ b/src/routes/chat/ChannelList/NavItem.js @@ -2,6 +2,7 @@ import { NavLink, useNavigate } from "react-router-dom"; import { useDrop } from "react-dnd"; import { NativeTypes } from "react-dnd-html5-backend"; import { useDispatch, useSelector } from "react-redux"; +// import { useDebounce} from "rooks"; import { toggleChannelSetting } from "../../../app/slices/ui"; import ChannelIcon from "../../../common/component/ChannelIcon"; import { getUnreadCount } from "../utils"; @@ -9,13 +10,18 @@ import { getUnreadCount } from "../utils"; const NavItem = ({ id, setFiles, contextMenuEventHandler }) => { const dispatch = useDispatch(); const navigate = useNavigate(); - const { channel, mids, messageData } = useSelector((store) => { - return { - channel: store.channels.byId[id], - mids: store.channelMessage[id], - messageData: store.message, - }; - }); + // const getUnreadCountDebounced=useDebounce(getUnreadCount,300) + const { channel, mids, messageData, readIndex, loginUid } = useSelector( + (store) => { + return { + channel: store.channels.byId[id], + mids: store.channelMessage[id], + messageData: store.message, + loginUid: store.authData.uid, + readIndex: store.footprint.readChannels[id], + }; + } + ); const handleChannelSetting = (evt) => { evt.preventDefault(); evt.stopPropagation(); @@ -39,7 +45,7 @@ const NavItem = ({ id, setFiles, contextMenuEventHandler }) => { }), })); const { is_public, name } = channel; - const unreads = getUnreadCount(mids, messageData); + const unreads = getUnreadCount({ mids, messageData, readIndex, loginUid }); return ( { + const { msgIds, currUser, messageData } = useSelector((store) => { return { currUser: store.contacts.byId[uid], msgIds: store.userMessage.byId[uid] || [], messageData: store.message, - readUsers: store.footprint.readUser || {}, }; }); const ref = useChatScroll(msgIds); @@ -33,7 +32,6 @@ export default function DMChat({ uid = "", dropFiles = [] }) { if (!currUser) return null; // console.log("user msgs", msgs); - const readIndex = readUsers[uid] || 0; return ( { const curr = messageData[mid]; - const self = curr.from_uid == currUser.uid; - const read = self ? true : mid <= readIndex ? true : false; const prev = idx == 0 ? null : messageData[msgIds[idx - 1]]; return renderMessageFragment({ prev, curr, contextId: uid, - read, context: "user", }); })} diff --git a/src/routes/chat/DMList.js b/src/routes/chat/DMList.js index ad7022e6..a7df2e0f 100644 --- a/src/routes/chat/DMList.js +++ b/src/routes/chat/DMList.js @@ -5,7 +5,7 @@ import relativeTime from "dayjs/plugin/relativeTime"; import { useDrop } from "react-dnd"; import { NativeTypes } from "react-dnd-html5-backend"; import { useSelector } from "react-redux"; -import { renderPreviewMessage } from "./utils"; +import { renderPreviewMessage, getUnreadCount } from "./utils"; import Contact from "../../common/component/Contact"; dayjs.extend(relativeTime); const NavItem = ({ uid, mid, unreads, setFiles }) => { @@ -60,9 +60,32 @@ const NavItem = ({ uid, mid, unreads, setFiles }) => { ); }; -export default function DMList({ sessions, setDropFiles }) { - return sessions.map(({ uid, lastMid, unreads } = {}) => { - if (!uid) return null; +// mids: ChannelMsgData[channel_id], +// messageData, +// readIndex: readChannels[channel_id], +// loginUid, +export default function DMList({ uids, setDropFiles }) { + const { userMessage, messageData, readUsers, loginUid } = useSelector( + (store) => { + return { + loginUid: store.authData.uid, + readUsers: store.footprint.readUsers, + contactData: store.contacts.byId, + userMessage: store.userMessage.byId, + messageData: store.message, + }; + } + ); + const sessions = uids.map((uid) => { + const mids = userMessage[uid]; + const lastMid = [...mids].pop(); + const readIndex = readUsers[uid]; + const unreads = getUnreadCount({ mids, readIndex, messageData, loginUid }); + + return { lastMid, unreads, uid }; + }); + + return sessions.map(({ lastMid, uid, unreads }) => { return ( { + const { contactsData, sessionUids } = useSelector((store) => { return { contactsData: store.contacts.byId, - UserMsgData: store.userMessage, - messageData: store.message, + sessionUids: store.userMessage.ids, }; }); const [channelModalVisible, setChannelModalVisible] = useState(false); @@ -43,10 +41,6 @@ export default function ChatPage() { listEle.classList.toggle("collapse"); }; const tmpSessionUser = contactsData[user_id]; - const sessions = UserMsgData.ids.map((uid) => { - const unreads = getUnreadCount(UserMsgData.byId[uid], messageData); - return { uid, unreads, lastMid: [...UserMsgData.byId[uid]].pop() }; - }); return ( <> {channelModalVisible && ( @@ -95,8 +89,8 @@ export default function ChatPage() { />