jam-cloud/jam-ui/src/components/sessions/JKLobbyChat.js

217 lines
6.8 KiB
JavaScript

import React, { useState, useRef, useEffect } from 'react';
import { Container, Row, Col, Button } from 'reactstrap';
import { useDispatch, useSelector } from 'react-redux';
import { fetchLobbyChatMessages, postNewChatMessage } from '../../store/features/lobbyChatMessagesSlice';
import { useAuth } from '../../context/UserAuth';
import JKProfileAvatar from '../profile/JKProfileAvatar';
import TimeAgo from '../common/JKTimeAgo';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { useTranslation } from 'react-i18next';
import { useResponsive } from '@farfetch/react-context-responsive';
import useOnScreen from '../../hooks/useOnScreen';
import useKeepScrollPosition from '../../hooks/useKeepScrollPosition';
function JKLobbyChat() {
const CHANNEL_LOBBY = 'lobby';
const LIMIT = 10;
const [newMessage, setNewMessage] = useState('');
const dispatch = useDispatch();
const messageTextBox = useRef();
const scrollbar = useRef();
const { greaterThan } = useResponsive();
const scrolledToBottom = useRef(false);
const { currentUser } = useAuth();
const [fetching, setFetching] = useState(false);
const [messagesArrived, setMessagesArrived] = useState(false);
const [offset, setOffset] = useState(0);
const chatMessages = useSelector(state => state.lobbyChat.records.messages);
const next = useSelector(state => state.lobbyChat.records.next);
const createStatus = useSelector(state => state.lobbyChat.create_status);
const [messages, setMessages] = useState([]);
const [lastMessageRef, setLastMessageRef] = useState(null);
const isIntersecting = useOnScreen({ current: lastMessageRef });
const { containerRef } = useKeepScrollPosition([messages]);
const fetchMessages = async (overrides = {}) => {
const options = { start: offset * LIMIT, limit: LIMIT };
const params = { ...options, ...overrides };
try {
setFetching(true);
await dispatch(fetchLobbyChatMessages(params)).unwrap();
} catch (error) {
console.log('Error when fetching chat messages', error);
} finally {
setFetching(false);
}
};
useEffect(() => {
if (isIntersecting) {
if (next) {
setOffset(prev => prev + 1);
}
}
}, [isIntersecting]);
useEffect(() => {
if (offset !== 0) {
fetchMessages();
}
}, [offset]);
useEffect(() => {
fetchMessages();
}, []);
useEffect(() => {
setMessages(old => {
const chats = old.concat(chatMessages);
const deliveredChats = chats.filter((chat, index) => {
return chat.status !== 'pending';
});
return deliveredChats.sort((a, b) => {
return new Date(a.created_at) - new Date(b.created_at);
});
});
if (!scrollBarAtBottom(containerRef.current)) {
if (messages.length > 0 && messages[messages.length - 1]['user_id'] !== currentUser.id) {
setMessagesArrived(true);
}
}
}, [chatMessages]);
const scrollBarAtBottom = el => {
let sh = el.scrollHeight,
st = el.scrollTop,
ht = el.offsetHeight;
return ht == 0 || st == sh - ht;
};
const goToBottom = () => {
containerRef.current.scrollTop = containerRef.current.scrollHeight;
};
const handleOnKeyPress = event => {
if (event.key === 'Enter' || event.key === 'NumpadEnter') {
event.preventDefault();
sendMessage();
event.target.value = '';
}
};
const sendMessage = () => {
let msgData = {
id: Date.now(),
message: newMessage,
channel: CHANNEL_LOBBY,
user_id: currentUser.id,
created_at: new Date().getTime(),
user: {
id: currentUser.id,
name: currentUser.name,
photo_url: currentUser.photo_url
},
status: 'pending'
};
setNewMessage('');
setMessages(old => old.concat([msgData]));
goToBottom();
try {
dispatch(postNewChatMessage(msgData));
} catch (err) {
console.log('Error when posting new chat message', err);
}
};
useEffect(() => {
if (createStatus === 'succeeded') {
// fetchMessages({ start: 0, limit: 1, lastOnly: true });
setMessages([])
fetchMessages({ start: 0, limit: offset === 0 ? LIMIT : offset * LIMIT})
messageTextBox.current.focus();
}
}, [createStatus]);
const wrapperStyle = {
display: 'flex',
flexDirection: 'column',
height: '200'
};
const containerStyle = {
display: 'flex',
flexDirection: 'column',
height: '400px',
overflow: 'auto'
};
return (
<div>
<div className="bg-200 text-900" style={{ padding: '0.75rem' }}>
Lobby Chat
</div>
<div className="border pt-1 pl-3 p-2" style={wrapperStyle}>
<div className="lobby-chat" ref={containerRef} style={containerStyle}>
{messages.map((message, i) => (
<div className="d-flex mb-3 mr-1 text-message-row" key={greaterThan ? `desktop_${message.id}` : `mobile_${message.id}`}>
<div className='d-flex align-items-center' ref={ref => (i === 0 ? setLastMessageRef(ref) : null)}>
<div className="avatar avatar-2xl">
<JKProfileAvatar url={message.user.photo_url} />
</div>
<div className="pt-2">
<div className="d-flex flex-column">
<div>
<strong>{message.user.name}</strong>
<time className="notification-time ml-2 t-1">
<TimeAgo date={message.created_at} />
</time>
{message.status === 'pending' && (
<span className="ml-2">
<FontAwesomeIcon icon="spinner" />
</span>
)}
</div>
<div>
{message.message}
{/* <hr />
{message.id} */}
</div>
</div>
</div>
</div>
</div>
))}
{messagesArrived && (
<div className="d-flex justify-content-center">
<Button color="info" size="sm" onClick={() => setMessagesArrived(prev => !prev)}>
New messages
</Button>
</div>
)}
</div>
<div className="mt-2" style={{ height: '20%' }}>
<textarea
style={{ width: '100%' }}
value={newMessage}
onChange={e => setNewMessage(e.target.value)}
onKeyPress={handleOnKeyPress}
ref={messageTextBox}
/>
</div>
<div className="d-flex justify-content-end" style={{ height: '10%' }}>
<Button color="primary" onClick={sendMessage} disabled={!newMessage}>
Send Message
</Button>
</div>
</div>
</div>
);
}
export default JKLobbyChat;