chat performance improvements

This commit is contained in:
PhilReact 2025-02-26 02:41:40 +02:00
parent cbdb28c5d5
commit 44dd926869
8 changed files with 237 additions and 236 deletions

View File

@ -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) {

View File

@ -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 `<a href="${DOMPurify.sanitize(href)}" class="auto-link">${DOMPurify.sanitize(url)}</a>`;
});
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(/<span[^>]*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 `<a href="${DOMPurify.sanitize(href)}" class="auto-link">${DOMPurify.sanitize(url)}</a>`;
});
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(/<span[^>]*data-url="qortal:\/\/use-embed\/[^"]*"[^>]*>.*?<\/span>/g, '');
}, [])
const handleClick = async (e) => {
e.preventDefault();

View File

@ -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 (
<>
<MessageWragger lastMessage={lastSignature === message?.signature} isLast={isLast} onSeen={onSeenFunc}>
{message?.divide && (
<div className="unread-divider" id="unread-divider-id">
Unread messages below
</div>
)}
<div
ref={lastSignature === message?.signature ? ref : null}
style={{
padding: "10px",
backgroundColor: "#232428",
@ -132,25 +174,25 @@ useEffect(()=> {
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)}
</Avatar>
</WrapperUserAction>
<Tooltip disableFocusListener title={`level ${userInfo?.level}`}>
<Tooltip disableFocusListener title={`level ${userInfo}`}>
<img style={{
visibility: userInfo?.level !== undefined ? 'visible' : 'hidden',
visibility: userInfo !== undefined ? 'visible' : 'hidden',
width: '30px',
height: 'auto'
}} src={getBadgeImg(userInfo?.level)} />
}} src={getBadgeImg(userInfo)} />
</Tooltip>
</Box>
)}
@ -257,13 +299,7 @@ useEffect(()=> {
}}>Replied to {reply?.senderName || reply?.senderAddress}</Typography>
{reply?.messageText && (
<MessageDisplay
htmlContent={generateHTML(reply?.messageText, [
StarterKit,
Underline,
Highlight,
Mention,
TextStyle
])}
htmlContent={htmlReply}
/>
)}
{reply?.decryptedData?.type === "notification" ? (
@ -275,17 +311,11 @@ useEffect(()=> {
</Box>
</>
)}
{message?.messageText && (
<MessageDisplay
htmlContent={generateHTML(message?.messageText, [
StarterKit,
Underline,
Highlight,
Mention,
TextStyle
])}
htmlContent={htmlText}
/>
)}
{message?.decryptedData?.type === "notification" ? (
<MessageDisplay htmlContent={message.decryptedData?.data?.message} />
) : (
@ -457,21 +487,11 @@ useEffect(()=> {
</Box>
</Box>
{/* <Message
model={{
direction: 'incoming',
message: message.text,
position: 'single',
sender: message.senderName,
sentTime: message.timestamp
}}
></Message> */}
{/* {!message.unread && <span style={{ color: 'green' }}> Seen</span>} */}
</div>
</>
</MessageWragger>
);
};
});
export const ReplyPreview = ({message, isEdit})=> {
@ -530,4 +550,36 @@ export const ReplyPreview = ({message, isEdit})=> {
</Box>
)
}
const MessageWragger = ({lastMessage, onSeen, isLast, children})=> {
if(lastMessage){
return (
<WatchComponent onSeen={onSeen} isLast={isLast}>{children}</WatchComponent>
)
}
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 <div ref={ref} style={{
width: '100%',
display: 'flex',
justifyContent: 'center'
}}>
{children}
</div>
}

View File

@ -118,7 +118,7 @@ export const DesktopHeader = ({
fontWeight: 600,
}}
>
{selectedGroup?.groupName}
{selectedGroup?.groupId === '0' ? 'General' :selectedGroup?.groupName}
</Typography>
</Box>
<Box
@ -126,76 +126,10 @@ export const DesktopHeader = ({
display: "flex",
gap: "20px",
alignItems: "center",
visibility: selectedGroup?.groupId === '0' ? 'hidden' : 'visibile'
}}
>
{/* <ButtonBase
onClick={() => {
goToHome();
}}
>
<IconWrapper
color="rgba(250, 250, 250, 0.5)"
label="Home"
selected={isHome}
>
<HomeIcon
height={25}
color={isHome ? "white" : "rgba(250, 250, 250, 0.5)"}
/>
</IconWrapper>
</ButtonBase>
<ButtonBase
onClick={() => {
setDesktopSideView("groups");
}}
>
<IconWrapper
color="rgba(250, 250, 250, 0.5)"
label="Groups"
selected={isGroups}
>
<HubsIcon
height={25}
color={
hasUnreadGroups
? "var(--danger)"
: isGroups
? "white"
: "rgba(250, 250, 250, 0.5)"
}
/>
</IconWrapper>
</ButtonBase>
<ButtonBase
onClick={() => {
setDesktopSideView("directs");
}}
>
<IconWrapper
color="rgba(250, 250, 250, 0.5)"
label="Messaging"
selected={isDirects}
>
<MessagingIcon
height={25}
color={
hasUnreadDirects
? "var(--danger)"
: isDirects
? "white"
: "rgba(250, 250, 250, 0.5)"
}
/>
</IconWrapper>
</ButtonBase> */}
{/* <Box
sx={{
width: "1px",
height: "50px",
background: "white",
borderRadius: "50px",
}}
/> */}
<ButtonBase
onClick={() => {
goToAnnouncements()

View File

@ -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 = ({
</ListItemAvatar>
<ListItemText
primary={group.groupName}
primary={group.groupId === '0' ? 'General' : group.groupName}
secondary={!group?.timestamp ? 'no messages' :`last message: ${formatEmailDate(group?.timestamp)}`}
primaryTypographyProps={{
style: {

View File

@ -78,7 +78,15 @@ export const WebSocketActive = ({ myAddress, setIsLoadingGroups }) => {
}
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'

View File

@ -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,

View File

@ -46,81 +46,83 @@ export const WrapperUserAction = ({ children, address, name, disabled }) => {
</Box>
{/* Popover */}
<Popover
id={id}
open={open}
anchorEl={anchorEl}
onClose={handleClose} // Close popover on click outside
anchorOrigin={{
vertical: 'bottom',
horizontal: 'center',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'center',
}}
componentsProps={{
paper: {
onClick: (event) => event.stopPropagation(), // Stop propagation inside popover
},
}}
>
<Box sx={{ p: 2, display: 'flex', flexDirection: 'column', gap: 1 }}>
{/* Option 1: Message */}
<Button
variant="text"
onClick={() => {
handleClose();
setTimeout(() => {
executeEvent('openDirectMessageInternal', {
address,
name,
});
}, 200);
}}
sx={{
color: 'white',
justifyContent: 'flex-start'
}}
>
Message
</Button>
{/* Option 2: Send QORT */}
<Button
variant="text"
onClick={() => {
executeEvent('openPaymentInternal', {
address,
name,
});
handleClose();
}}
sx={{
color: 'white',
justifyContent: 'flex-start'
}}
>
Send QORT
</Button>
<Button
variant="text"
onClick={() => {
navigator.clipboard.writeText(address|| "");
handleClose();
}}
sx={{
color: 'white',
justifyContent: 'flex-start'
}}
>
Copy address
</Button>
</Box>
</Popover>
{open && (
<Popover
id={id}
open={open}
anchorEl={anchorEl}
onClose={handleClose} // Close popover on click outside
anchorOrigin={{
vertical: 'bottom',
horizontal: 'center',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'center',
}}
componentsProps={{
paper: {
onClick: (event) => event.stopPropagation(), // Stop propagation inside popover
},
}}
>
<Box sx={{ p: 2, display: 'flex', flexDirection: 'column', gap: 1 }}>
{/* Option 1: Message */}
<Button
variant="text"
onClick={() => {
handleClose();
setTimeout(() => {
executeEvent('openDirectMessageInternal', {
address,
name,
});
}, 200);
}}
sx={{
color: 'white',
justifyContent: 'flex-start'
}}
>
Message
</Button>
{/* Option 2: Send QORT */}
<Button
variant="text"
onClick={() => {
executeEvent('openPaymentInternal', {
address,
name,
});
handleClose();
}}
sx={{
color: 'white',
justifyContent: 'flex-start'
}}
>
Send QORT
</Button>
<Button
variant="text"
onClick={() => {
navigator.clipboard.writeText(address|| "");
handleClose();
}}
sx={{
color: 'white',
justifyContent: 'flex-start'
}}
>
Copy address
</Button>
</Box>
</Popover>
)}
</>
);
};