import { IonCol, IonContent, IonFabButton, IonGrid, IonIcon, IonInput, IonRow, IonToolbar } from '@ionic/react'; import { sendOutline } from 'ionicons/icons'; import React, { useEffect, useRef, useState } from 'react'; import { db, rtdb } from '../services/firebase'; import { secondsToTimestamp } from '../services/utilities'; import './Messages.css'; type MessagesProps = { pane: string; ownerId: string; roomId: string; userId: string; userList: Map; joinTime: number; }; type Message = { content: string; createdAt: number; id: string; senderId: string; type: string; }; const Messages: React.FC = ({ pane, ownerId, roomId, userId, userList, joinTime }) => { const [chats, setChats] = useState([]); // All processed chat messages const [systemMessages, setSystemMessages] = useState([]); // All processed system messages const [allMessages, setAllMessages] = useState([]); // Combined array of chat and system messages const [userHistory] = useState>(new Map()); // All users who are/were in the room const [message, setMessage] = useState(''); // Message to be sent const contentRef = useRef(null); // Listen for new chat messages useEffect(() => { rtdb.ref('/chats/' + roomId).on('value', (snapshot) => { let arr: Message[] = []; snapshot.forEach((child) => { const msg = child.val(); if (msg.createdAt > joinTime) { arr.push({ content: msg.content, createdAt: msg.createdAt, id: msg.senderId + msg.createdAt, senderId: msg.senderId, type: 'chat', }); } }); setChats(arr); }); return () => { rtdb.ref('/chats/' + roomId).off('value'); }; }, [roomId, joinTime]); // Listen for new system messages useEffect(() => { const roomUnsubscribe = db .collection('rooms') .doc(roomId) .onSnapshot((docSnapshot) => { const docData = docSnapshot.data(); if (docData !== undefined) { const requests = docData.requests; let arr: Message[] = []; for (const req of requests) { if (req.createdAt > joinTime && req.type !== 'updateState') { arr.push({ content: processType(req.type, req.data), createdAt: req.createdAt, id: req.senderId + req.createdAt, senderId: req.senderId, type: req.type, }); } } setSystemMessages(arr); } }); return () => { roomUnsubscribe(); }; }, [roomId, joinTime]); // Convert request type to message content const processType = (type: string, data: any): string => { switch (type) { case 'change': return 'changed the video.'; case 'join': return 'joined the room.'; case 'pause': return 'paused the video at ' + secondsToTimestamp(data); case 'play': return 'played the video from ' + secondsToTimestamp(data); case 'nameChange': return data.prev + ' changed their name to ' + data.curr; default: return ''; } }; // Maintain list of users who entered the room, in order to keep all sender names of messages in the room useEffect(() => { userList.forEach((name: string, id: string) => { userHistory.set(id, name); }); }, [userList, userHistory]); // Combine messages useEffect(() => { setAllMessages(chats.concat(systemMessages)); }, [chats, systemMessages]); // Always scroll to most recent chat message (bottom) useEffect(() => { let content = contentRef.current; // Set timeout because DOM doesn't update immediately after 'chats' state is updated setTimeout(() => { content?.scrollToBottom(200); }, 100); }, [allMessages, pane]); // Retrieve display name from userId const getName = (id: string) => { let name = userHistory.get(id); if (id === ownerId) { name += ' 👑'; } return name; }; // Send message to database const sendMessage = async () => { if (message !== '') { await rtdb.ref('/chats/' + roomId).push({ content: message, createdAt: Date.now(), senderId: userId, }); // Reset textarea field setMessage(''); } }; const onEnter = (e: React.KeyboardEvent) => { if (e.key === 'Enter') { sendMessage(); } }; const renderMessages = () => { return allMessages .sort((msg1, msg2) => msg1.createdAt - msg2.createdAt) .map((msg) => { if (getName(msg.senderId) !== undefined) { if (msg.type === 'chat') { return ( {getName(msg.senderId) !== '' ? {getName(msg.senderId)}: : <>} {msg.content} ); } else { return ( {msg.type === 'nameChange' ? msg.content : getName(msg.senderId) + ' ' + msg.content} ); } } else { return <>; } }); }; return ( <> {userList.size === 0 ? ( Loading... ) : ( <> {ownerId === userId ? ( Welcome to TurtleTV! You joined the room as the owner, so everyone else in the room will sync up with your plays/pauses. ) : ( Welcome to TurtleTV! You joined the room as a member, so your plays/pauses will not affect anyone else in the room. )} {renderMessages()} )} setMessage(e.detail.value!)} onKeyDown={(e) => onEnter(e)} value={message} placeholder="Send message" enterkeyhint="send" autocorrect="on" autocapitalize="on" spellcheck={true} class="message-input" > ); }; export default Messages;