From 44dd92686988c1e375aa6141b57b07544cb9cf09 Mon Sep 17 00:00:00 2001 From: PhilReact Date: Wed, 26 Feb 2025 02:41:40 +0200 Subject: [PATCH] chat performance improvements --- src/background.ts | 3 +- src/components/Chat/MessageDisplay.tsx | 52 +++---- src/components/Chat/MessageItem.tsx | 156 ++++++++++++++------- src/components/Desktop/DesktopHeader.tsx | 72 +--------- src/components/Group/Group.tsx | 4 +- src/components/Group/WebsocketActive.tsx | 10 +- src/components/Group/useHandleUserInfo.tsx | 24 ++-- src/components/WrapperUserAction.tsx | 152 ++++++++++---------- 8 files changed, 237 insertions(+), 236 deletions(-) diff --git a/src/background.ts b/src/background.ts index fe104b7..634c280 100644 --- a/src/background.ts +++ b/src/background.ts @@ -673,7 +673,7 @@ const handleNotification = async (groups) => { let mutedGroups = (await getUserSettings({ key: "mutedGroups" })) || []; if (!isArray(mutedGroups)) mutedGroups = []; - + mutedGroups.push('0') let isFocused; const data = groups.filter( (group) => @@ -3182,6 +3182,7 @@ export const checkNewMessages = async () => { try { let mutedGroups = (await getUserSettings({ key: "mutedGroups" })) || []; if (!isArray(mutedGroups)) mutedGroups = []; + mutedGroups.push('0') let myName = ""; const userData = await getUserInfo(); if (userData?.name) { diff --git a/src/components/Chat/MessageDisplay.tsx b/src/components/Chat/MessageDisplay.tsx index ce55803..afc1b8a 100644 --- a/src/components/Chat/MessageDisplay.tsx +++ b/src/components/Chat/MessageDisplay.tsx @@ -1,4 +1,4 @@ -import React, { useEffect } from 'react'; +import React, { useEffect, useMemo } from 'react'; import DOMPurify from 'dompurify'; import './styles.css'; import { executeEvent } from '../../utils/events'; @@ -63,30 +63,34 @@ function processText(input) { return wrapper.innerHTML; } -export const MessageDisplay = ({ htmlContent, isReply }) => { - const linkify = (text) => { - if (!text) return ""; // Return an empty string if text is null or undefined - - let textFormatted = text; - const urlPattern = /(\bhttps?:\/\/[^\s<]+|\bwww\.[^\s<]+)/g; - textFormatted = text.replace(urlPattern, (url) => { - const href = url.startsWith('http') ? url : `https://${url}`; - return `${DOMPurify.sanitize(url)}`; - }); - return processText(textFormatted); - }; - +const linkify = (text) => { + if (!text) return ""; // Return an empty string if text is null or undefined - const sanitizedContent = DOMPurify.sanitize(linkify(htmlContent), { - ALLOWED_TAGS: [ - 'a', 'b', 'i', 'em', 'strong', 'p', 'br', 'div', 'span', 'img', - 'ul', 'ol', 'li', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'code', 'pre', 'table', 'thead', 'tbody', 'tr', 'th', 'td', 's', 'hr' - ], - ALLOWED_ATTR: [ - 'href', 'target', 'rel', 'class', 'src', 'alt', 'title', - 'width', 'height', 'style', 'align', 'valign', 'colspan', 'rowspan', 'border', 'cellpadding', 'cellspacing', 'data-url' - ], - }).replace(/]*data-url="qortal:\/\/use-embed\/[^"]*"[^>]*>.*?<\/span>/g, '');; + let textFormatted = text; + const urlPattern = /(\bhttps?:\/\/[^\s<]+|\bwww\.[^\s<]+)/g; + textFormatted = text.replace(urlPattern, (url) => { + const href = url.startsWith('http') ? url : `https://${url}`; + return `${DOMPurify.sanitize(url)}`; + }); + return processText(textFormatted); +}; + + +export const MessageDisplay = ({ htmlContent, isReply }) => { + + + const sanitizedContent = useMemo(()=> { + return DOMPurify.sanitize(linkify(htmlContent), { + ALLOWED_TAGS: [ + 'a', 'b', 'i', 'em', 'strong', 'p', 'br', 'div', 'span', 'img', + 'ul', 'ol', 'li', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'code', 'pre', 'table', 'thead', 'tbody', 'tr', 'th', 'td', 's', 'hr' + ], + ALLOWED_ATTR: [ + 'href', 'target', 'rel', 'class', 'src', 'alt', 'title', + 'width', 'height', 'style', 'align', 'valign', 'colspan', 'rowspan', 'border', 'cellpadding', 'cellspacing', 'data-url' + ], + }).replace(/]*data-url="qortal:\/\/use-embed\/[^"]*"[^>]*>.*?<\/span>/g, ''); + }, []) const handleClick = async (e) => { e.preventDefault(); diff --git a/src/components/Chat/MessageItem.tsx b/src/components/Chat/MessageItem.tsx index e325840..1fd7203 100644 --- a/src/components/Chat/MessageItem.tsx +++ b/src/components/Chat/MessageItem.tsx @@ -1,5 +1,5 @@ import { Message } from "@chatscope/chat-ui-kit-react"; -import React, { useContext, useEffect, useState } from "react"; +import React, { useCallback, useContext, useEffect, useMemo, useState } from "react"; import { useInView } from "react-intersection-observer"; import { MessageDisplay } from "./MessageDisplay"; import { Avatar, Box, Button, ButtonBase, List, ListItem, ListItemText, Popover, Tooltip, Typography } from "@mui/material"; @@ -50,7 +50,7 @@ const getBadgeImg = (level)=> { default: return level0Img } } -export const MessageItem = ({ +export const MessageItem = React.memo(({ message, onSeen, isLast, @@ -68,36 +68,78 @@ export const MessageItem = ({ onEdit, isPrivate }) => { - const { ref, inView } = useInView({ - threshold: 0.7, // Fully visible - triggerOnce: false, // Only trigger once when it becomes visible - }); + const {getIndividualUserInfo} = useContext(MyContext) const [anchorEl, setAnchorEl] = useState(null); const [selectedReaction, setSelectedReaction] = useState(null); - const userInfo = useRecoilValue(addressInfoKeySelector(message?.sender)); + const [userInfo, setUserInfo] = useState(null) - useEffect(() => { - if (inView && isLast && onSeen) { - onSeen(message.id); - } - }, [inView, message.id, isLast]); useEffect(()=> { - if(message?.sender){ - getIndividualUserInfo(message?.sender) + const getInfo = async ()=> { + if(!message?.sender) return + try { + const res = await getIndividualUserInfo(message?.sender) + if(!res) return null + setUserInfo(res) + } catch (error) { + // + } } -}, [message?.sender]) + + getInfo() +}, [message?.sender, getIndividualUserInfo]) + +const htmlText = useMemo(()=> { + + if(message?.messageText){ + return generateHTML(message?.messageText, [ + StarterKit, + Underline, + Highlight, + Mention, + TextStyle + ]) + } + +}, []) + + + +const htmlReply = useMemo(()=> { + + if(reply?.messageText){ + return generateHTML(reply?.messageText, [ + StarterKit, + Underline, + Highlight, + Mention, + TextStyle + ]) + } + +}, []) + +const userAvatarUrl = useMemo(()=> { + return message?.senderName ? `${getBaseApiReact()}/arbitrary/THUMBNAIL/${ + message?.senderName + }/qortal_avatar?async=true` : '' +}, []) + +const onSeenFunc = useCallback(()=> { + onSeen(message.id); +}, [message?.id]) + return ( - <> + + {message?.divide && (
Unread messages below
)}
{ sx={{ backgroundColor: "#27282c", color: "white", + height: '40px', + width: '40px' }} alt={message?.senderName} - src={message?.senderName ? `${getBaseApiReact()}/arbitrary/THUMBNAIL/${ - message?.senderName - }/qortal_avatar?async=true` : ''} + src={userAvatarUrl} > {message?.senderName?.charAt(0)} - + + }} src={getBadgeImg(userInfo)} /> )} @@ -257,13 +299,7 @@ useEffect(()=> { }}>Replied to {reply?.senderName || reply?.senderAddress} {reply?.messageText && ( )} {reply?.decryptedData?.type === "notification" ? ( @@ -275,17 +311,11 @@ useEffect(()=> { )} - {message?.messageText && ( + - )} + {message?.decryptedData?.type === "notification" ? ( ) : ( @@ -457,21 +487,11 @@ useEffect(()=> { - {/* */} - {/* {!message.unread && Seen} */} +
- +
); -}; +}); export const ReplyPreview = ({message, isEdit})=> { @@ -530,4 +550,36 @@ export const ReplyPreview = ({message, isEdit})=> { ) +} + +const MessageWragger = ({lastMessage, onSeen, isLast, children})=> { + + if(lastMessage){ + return ( + {children} + ) + } + return children +} + +const WatchComponent = ({onSeen, isLast, children})=> { + const { ref, inView } = useInView({ + threshold: 0.7, // Fully visible + triggerOnce: true, // Only trigger once when it becomes visible + }); + + useEffect(() => { + if (inView && isLast && onSeen) { + onSeen(); + } + }, [inView, isLast, onSeen]); + + return
+ {children} +
+ } \ No newline at end of file diff --git a/src/components/Desktop/DesktopHeader.tsx b/src/components/Desktop/DesktopHeader.tsx index 6c256e8..4820054 100644 --- a/src/components/Desktop/DesktopHeader.tsx +++ b/src/components/Desktop/DesktopHeader.tsx @@ -118,7 +118,7 @@ export const DesktopHeader = ({ fontWeight: 600, }} > - {selectedGroup?.groupName} + {selectedGroup?.groupId === '0' ? 'General' :selectedGroup?.groupName} - {/* { - goToHome(); - }} - > - - - - - { - setDesktopSideView("groups"); - }} - > - - - - - { - setDesktopSideView("directs"); - }} - > - - - - */} - {/* */} + { goToAnnouncements() diff --git a/src/components/Group/Group.tsx b/src/components/Group/Group.tsx index 75cd960..73d80ba 100644 --- a/src/components/Group/Group.tsx +++ b/src/components/Group/Group.tsx @@ -447,12 +447,14 @@ export const Group = ({ const setUserInfoForLevels = useSetRecoilState(addressInfoControllerAtom); const isPrivate = useMemo(()=> { + if(selectedGroup?.groupId === '0') return false if(!selectedGroup?.groupId || !groupsProperties[selectedGroup?.groupId]) return null if(groupsProperties[selectedGroup?.groupId]?.isOpen === true) return false if(groupsProperties[selectedGroup?.groupId]?.isOpen === false) return true return null }, [selectedGroup]) + const setSelectedGroupId = useSetRecoilState(selectedGroupIdAtom) const toggleSideViewDirects = ()=> { if(isOpenSideViewGroups){ @@ -1937,7 +1939,7 @@ export const Group = ({ { } const data = JSON.parse(e.data); - const filteredGroups = data.groups?.filter(item => item?.groupId !== 0) || []; + const copyGroups = [...(data?.groups || [])] + const findIndex = copyGroups?.findIndex(item => item?.groupId === 0) + if(findIndex !== -1){ + copyGroups[findIndex] = { + ...(copyGroups[findIndex] || {}), + groupId: "0" + } + } + const filteredGroups = copyGroups const sortedGroups = filteredGroups.sort((a, b) => (b.timestamp || 0) - (a.timestamp || 0)); const sortedDirects = (data?.direct || []).filter(item => item?.name !== 'extension-proxy' && item?.address !== 'QSMMGSgysEuqDCuLw3S4cHrQkBrh3vP3VH' diff --git a/src/components/Group/useHandleUserInfo.tsx b/src/components/Group/useHandleUserInfo.tsx index db6993a..a497259 100644 --- a/src/components/Group/useHandleUserInfo.tsx +++ b/src/components/Group/useHandleUserInfo.tsx @@ -1,34 +1,32 @@ -import React, { useCallback, useEffect, useState } from "react"; +import React, { useCallback, useRef } from "react"; import { getBaseApiReact } from "../../App"; -import { useRecoilState, useSetRecoilState } from "recoil"; -import { addressInfoControllerAtom } from "../../atoms/global"; export const useHandleUserInfo = () => { - const [userInfo, setUserInfo] = useRecoilState(addressInfoControllerAtom); - + const userInfoRef = useRef({}) const getIndividualUserInfo = useCallback(async (address)=> { try { - if(!address || userInfo[address]) return + if(!address) return null + if(userInfoRef.current[address] !== undefined) return userInfoRef.current[address] + const url = `${getBaseApiReact()}/addresses/${address}`; const response = await fetch(url); if (!response.ok) { throw new Error("network error"); } const data = await response.json(); - setUserInfo((prev)=> { - return { - ...prev, - [address]: data - } - }) + userInfoRef.current = { + ...userInfoRef.current, + [address]: data?.level + } + return data?.level } catch (error) { //error } - }, [userInfo]) + }, []) return { getIndividualUserInfo, diff --git a/src/components/WrapperUserAction.tsx b/src/components/WrapperUserAction.tsx index 8758c99..f7af63e 100644 --- a/src/components/WrapperUserAction.tsx +++ b/src/components/WrapperUserAction.tsx @@ -46,81 +46,83 @@ export const WrapperUserAction = ({ children, address, name, disabled }) => { {/* Popover */} - event.stopPropagation(), // Stop propagation inside popover - }, - }} - > - - {/* Option 1: Message */} - - - {/* Option 2: Send QORT */} - - - - + {open && ( + event.stopPropagation(), // Stop propagation inside popover + }, + }} + > + + {/* Option 1: Message */} + + + {/* Option 2: Send QORT */} + + + + + )} ); };