import { Virtuoso } from 'react-virtuoso'; import { useState, useMemo, useRef, CSSProperties, FC } from 'react'; import { EditFilled } from '@ant-design/icons'; import { ConnectedClientInfoEvent, MessageType, NameChangeEvent, } from '../../../interfaces/socket-events'; import styles from './ChatContainer.module.scss'; import { ChatMessage } from '../../../interfaces/chat-message.model'; import { ChatUserMessage } from '../ChatUserMessage/ChatUserMessage'; import { ChatTextField } from '../ChatTextField/ChatTextField'; import { ChatModeratorNotification } from '../ChatModeratorNotification/ChatModeratorNotification'; // import ChatActionMessage from '../ChatAction/ChatActionMessage'; import { ChatSystemMessage } from '../ChatSystemMessage/ChatSystemMessage'; import { ChatJoinMessage } from '../ChatJoinMessage/ChatJoinMessage'; import { ScrollToBotBtn } from './ScrollToBotBtn'; export type ChatContainerProps = { messages: ChatMessage[]; usernameToHighlight: string; chatUserId: string; isModerator: boolean; showInput?: boolean; height?: string; }; function shouldCollapseMessages(messages: ChatMessage[], index: number): boolean { if (messages.length < 2) { return false; } const message = messages[index]; const { user: { id }, } = message; const lastMessage = messages[index - 1]; if (lastMessage?.type !== MessageType.CHAT) { return false; } if (!lastMessage.timestamp || !message.timestamp) { return false; } const maxTimestampDelta = 1000 * 60 * 2; // 2 minutes const lastTimestamp = new Date(lastMessage.timestamp).getTime(); const thisTimestamp = new Date(message.timestamp).getTime(); if (thisTimestamp - lastTimestamp > maxTimestampDelta) { return false; } return id === lastMessage?.user.id; } function checkIsModerator(message: ChatMessage | ConnectedClientInfoEvent) { const { user: { scopes }, } = message; if (!scopes || scopes.length === 0) { return false; } return scopes.includes('MODERATOR'); } export const ChatContainer: FC = ({ messages, usernameToHighlight, chatUserId, isModerator, showInput, height, }) => { const [atBottom, setAtBottom] = useState(false); // const [showButton, setShowButton] = useState(false); const chatContainerRef = useRef(null); // const spinIcon = ; const getNameChangeViewForMessage = (message: NameChangeEvent) => { const { oldName, user } = message; const { displayName, displayColor } = user; const color = `var(--theme-color-users-${displayColor})`; return (
{oldName} is now known as {displayName}
); }; const getUserJoinedMessage = (message: ChatMessage) => { const { user: { displayName, displayColor }, } = message; const isAuthorModerator = checkIsModerator(message); return ( ); }; const getConnectedInfoMessage = (message: ConnectedClientInfoEvent) => { const modStatusUpdate = checkIsModerator(message); if (!modStatusUpdate) { // Important note: We can't return null or an element with zero width // or zero height. So to work around this we return a very small 1x1 div. const st: CSSProperties = { width: '1px', height: '1px' }; return
; } // Alert the user that they are a moderator. return ; }; const getViewForMessage = ( index: number, message: ChatMessage | NameChangeEvent | ConnectedClientInfoEvent, ) => { switch (message.type) { case MessageType.CHAT: return ( ); case MessageType.NAME_CHANGE: return getNameChangeViewForMessage(message as NameChangeEvent); case MessageType.CONNECTED_USER_INFO: return getConnectedInfoMessage(message); case MessageType.USER_JOINED: return getUserJoinedMessage(message as ChatMessage); case MessageType.SYSTEM: return ( ); default: return null; } }; const MessagesTable = useMemo( () => ( <> getViewForMessage(index, message)} followOutput="auto" alignToBottom atBottomStateChange={bottom => setAtBottom(bottom)} /> {!atBottom && } ), [messages, usernameToHighlight, chatUserId, isModerator, atBottom], ); return (
{MessagesTable} {showInput && }
); }; ChatContainer.defaultProps = { showInput: true, height: 'auto', };