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, useTheme, } from '@mui/material'; import { formatTimestamp } from '../../utils/time'; import { MyContext, getBaseApiReact } from '../../App'; import { generateHTML } from '@tiptap/react'; import Highlight from '@tiptap/extension-highlight'; import Mention from '@tiptap/extension-mention'; import StarterKit from '@tiptap/starter-kit'; import Underline from '@tiptap/extension-underline'; import { WrapperUserAction } from '../WrapperUserAction'; import ReplyIcon from '@mui/icons-material/Reply'; import { Spacer } from '../../common/Spacer'; import { ReactionPicker } from '../ReactionPicker'; import KeyOffIcon from '@mui/icons-material/KeyOff'; import EditIcon from '@mui/icons-material/Edit'; import TextStyle from '@tiptap/extension-text-style'; import level0Img from '../../assets/badges/level-0.png'; import level1Img from '../../assets/badges/level-1.png'; import level2Img from '../../assets/badges/level-2.png'; import level3Img from '../../assets/badges/level-3.png'; import level4Img from '../../assets/badges/level-4.png'; import level5Img from '../../assets/badges/level-5.png'; import level6Img from '../../assets/badges/level-6.png'; import level7Img from '../../assets/badges/level-7.png'; import level8Img from '../../assets/badges/level-8.png'; import level9Img from '../../assets/badges/level-9.png'; import level10Img from '../../assets/badges/level-10.png'; const getBadgeImg = (level) => { switch (level?.toString()) { case '0': return level0Img; case '1': return level1Img; case '2': return level2Img; case '3': return level3Img; case '4': return level4Img; case '5': return level5Img; case '6': return level6Img; case '7': return level7Img; case '8': return level8Img; case '9': return level9Img; case '10': return level10Img; default: return level0Img; } }; export const MessageItem = React.memo( ({ message, onSeen, isLast, isTemp, myAddress, onReply, isShowingAsReply, reply, replyIndex, scrollToItem, handleReaction, reactions, isUpdating, lastSignature, onEdit, isPrivate, }) => { const { getIndividualUserInfo } = useContext(MyContext); const [anchorEl, setAnchorEl] = useState(null); const [selectedReaction, setSelectedReaction] = useState(null); const [userInfo, setUserInfo] = useState(null); useEffect(() => { const getInfo = async () => { if (!message?.sender) return; try { const res = await getIndividualUserInfo(message?.sender); if (!res) return null; setUserInfo(res); } catch (error) { // } }; getInfo(); }, [message?.sender, getIndividualUserInfo]); const htmlText = useMemo(() => { if (message?.messageText) { return generateHTML(message?.messageText, [ StarterKit, Underline, Highlight, Mention, TextStyle, ]); } }, [message?.editTimestamp]); const htmlReply = useMemo(() => { if (reply?.messageText) { return generateHTML(reply?.messageText, [ StarterKit, Underline, Highlight, Mention, TextStyle, ]); } }, [reply?.editTimestamp]); const userAvatarUrl = useMemo(() => { return message?.senderName ? `${getBaseApiReact()}/arbitrary/THUMBNAIL/${ message?.senderName }/qortal_avatar?async=true` : ''; }, []); const onSeenFunc = useCallback(() => { onSeen(message.id); }, [message?.id]); const theme = useTheme(); return ( <> {message?.divide && (
Unread messages below
)}
{isShowingAsReply ? ( ) : ( {message?.senderName?.charAt(0)} )} {message?.senderName || message?.sender} {message?.sender === myAddress && (!message?.isNotEncrypted || isPrivate === false) && ( { onEdit(message); }} > )} {!isShowingAsReply && ( { onReply(message); }} > )} {!isShowingAsReply && handleReaction && ( { if ( reactions && reactions[val] && reactions[val]?.find( (item) => item?.sender === myAddress ) ) { handleReaction(val, message, false); } else { handleReaction(val, message, true); } }} /> )} {reply && ( <> { scrollToItem(replyIndex); }} > Replied to {reply?.senderName || reply?.senderAddress} {reply?.messageText && ( )} {reply?.decryptedData?.type === 'notification' ? ( ) : ( )} )} {htmlText && } {message?.decryptedData?.type === 'notification' ? ( ) : ( )} {reactions && Object.keys(reactions).map((reaction) => { const numberOfReactions = reactions[reaction]?.length; // const myReaction = reactions if (numberOfReactions === 0) return null; return ( { event.stopPropagation(); // Prevent event bubbling setAnchorEl(event.currentTarget); setSelectedReaction(reaction); }} >
{reaction}
{' '} {numberOfReactions > 1 && ( {' '} {numberOfReactions} )}
); })}
{selectedReaction && ( { setAnchorEl(null); setSelectedReaction(null); }} anchorOrigin={{ vertical: 'top', horizontal: 'center', }} transformOrigin={{ vertical: 'bottom', horizontal: 'center', }} PaperProps={{ // TODO: deprecated style: { backgroundColor: theme.palette.background.default, color: theme.palette.text.primary, }, }} > People who reacted with {selectedReaction} {reactions[selectedReaction]?.map((reactionItem) => ( ))} )} {message?.isNotEncrypted && isPrivate && ( )} {isUpdating ? ( {message?.status === 'failed-permanent' ? 'Failed to update' : 'Updating...'} ) : isTemp ? ( {message?.status === 'failed-permanent' ? 'Failed to send' : 'Sending...'} ) : ( <> {message?.isEdit && ( Edited )} {formatTimestamp(message.timestamp)} )}
); } ); export const ReplyPreview = ({ message, isEdit = false }) => { const theme = useTheme(); return ( {isEdit ? ( Editing Message ) : ( Replied to {message?.senderName || message?.senderAddress} )} {message?.messageText && ( )} {message?.decryptedData?.type === 'notification' ? ( ) : ( )} ); }; 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 delay: 100, trackVisibility: false, }); useEffect(() => { if (inView && isLast && onSeen) { setTimeout(() => { onSeen(); }, 100); } }, [inView, isLast, onSeen]); return (
{children}
); };