mirror of
https://gitlab.com/MrFry/qmining-page
synced 2026-04-28 03:07:36 +02:00
493 lines
12 KiB
JavaScript
493 lines
12 KiB
JavaScript
import React from 'react'
|
|
import Head from 'next/head'
|
|
import io from 'socket.io-client'
|
|
|
|
import constants from '../constants.json'
|
|
import LoadingIndicator from '../components/LoadingIndicator'
|
|
import styles from './chat.module.css'
|
|
|
|
function countAllMessages(msgs) {
|
|
return Object.keys(msgs).reduce((acc, key) => {
|
|
return acc + msgs[key].length
|
|
}, 0)
|
|
}
|
|
|
|
function groupMessages(msgs, currUser) {
|
|
return msgs.reduce((acc, msg) => {
|
|
const group =
|
|
parseInt(msg.sender) !== parseInt(currUser) ? msg.sender : msg.reciever
|
|
return {
|
|
...acc,
|
|
[group]: [msg],
|
|
}
|
|
}, {})
|
|
}
|
|
|
|
function addMsgsToGroup(msgGroup, msgs, user) {
|
|
let res = { ...msgGroup }
|
|
msgs.forEach((msg) => {
|
|
res = addMsgToGroup(
|
|
res,
|
|
msg.reciever === user ? { ...msg, unread: 0 } : msg,
|
|
user
|
|
)
|
|
})
|
|
return res
|
|
}
|
|
|
|
function addMsgToGroup(msgGroup, msg, user) {
|
|
const group =
|
|
parseInt(msg.sender) === parseInt(user) ? msg.reciever : msg.sender
|
|
if (!msgGroup[group]) {
|
|
msgGroup[group] = []
|
|
}
|
|
return {
|
|
...msgGroup,
|
|
[group]: [...msgGroup[group], msg],
|
|
}
|
|
}
|
|
|
|
function SeenMarker() {
|
|
return (
|
|
<span className={styles.unreadMarker} key={`unread_marker`}>
|
|
Látta
|
|
</span>
|
|
)
|
|
}
|
|
function NewMarker() {
|
|
return (
|
|
<span className={styles.newMarker} key={`unread_marker`}>
|
|
Új üzenet
|
|
</span>
|
|
)
|
|
}
|
|
|
|
export default class Chat extends React.Component {
|
|
constructor(props) {
|
|
super(props)
|
|
|
|
this.state = {
|
|
msgs: {},
|
|
currMsg: '',
|
|
connected: false,
|
|
selectedUser: 0,
|
|
}
|
|
if (props.refetchGlobalData) {
|
|
props.refetchGlobalData()
|
|
}
|
|
if (props.globalData && !isNaN(props.globalData.userId)) {
|
|
this.state.user = props.globalData.userId
|
|
// this.connect(this.props.globalData.userId)
|
|
}
|
|
}
|
|
|
|
componentDidUpdate(prevProps, prevState) {
|
|
if (!prevProps.globalData.userId && this.props.globalData.userId) {
|
|
this.setState({
|
|
user: this.props.globalData.userId,
|
|
})
|
|
// this.connect(this.props.globalData.userId)
|
|
}
|
|
|
|
if (
|
|
countAllMessages(prevState.msgs) !== countAllMessages(this.state.msgs) ||
|
|
prevState.selectedUser !== this.state.selectedUser
|
|
) {
|
|
this.scrollToChatBottom()
|
|
}
|
|
}
|
|
|
|
componentWillUnmount() {
|
|
console.info('Chat disconnect')
|
|
if (this.socket) {
|
|
this.socket.disconnect()
|
|
}
|
|
}
|
|
|
|
scrollToChatBottom() {
|
|
const objDiv = document.getElementById('messages')
|
|
if (objDiv) {
|
|
objDiv.scrollTop = objDiv.scrollHeight
|
|
}
|
|
}
|
|
|
|
handleErrors(err) {
|
|
alert(err.message)
|
|
console.error(err)
|
|
}
|
|
|
|
connect(user) {
|
|
const { connected } = this.state
|
|
if (connected) {
|
|
console.warn('Already connected ...')
|
|
return
|
|
}
|
|
// https://socket.io/docs/v4/handling-cors/#Configuration
|
|
const socket = io(`${constants.apiUrl}`, {
|
|
withCredentials: true,
|
|
extraHeaders: {
|
|
'qmining-chat': 'qmining-chat',
|
|
},
|
|
})
|
|
|
|
socket.on('connect', () => {
|
|
console.info(`Connected as user ${user}`)
|
|
socket.emit('join', { id: user })
|
|
})
|
|
socket.on('connect_error', (err) => this.handleErrors(err))
|
|
socket.on('connect_failed', (err) => this.handleErrors(err))
|
|
|
|
socket.on('prev messages', (data) => {
|
|
const { prevMsgs } = data
|
|
const { user } = this.state
|
|
this.setState({
|
|
msgs: groupMessages(prevMsgs, user),
|
|
connected: true,
|
|
})
|
|
})
|
|
|
|
socket.on('chat message read', (data) => {
|
|
const { userReadMsg } = data
|
|
this.partnerReadChatMessage(userReadMsg)
|
|
})
|
|
|
|
socket.on('chat message open', (data) => {
|
|
const { msgs, user, selectedUser } = this.state
|
|
if (msgs[selectedUser].length <= 1) {
|
|
this.setState({
|
|
msgs: addMsgsToGroup(msgs, data, user),
|
|
})
|
|
}
|
|
})
|
|
|
|
socket.on('chat message', (data) => {
|
|
const { msgs, user } = this.state
|
|
this.setState({
|
|
msgs: addMsgToGroup(msgs, data, user),
|
|
})
|
|
})
|
|
|
|
this.socket = socket
|
|
}
|
|
|
|
sendMsg() {
|
|
const { msgs, selectedUser, currMsg, user } = this.state
|
|
if (!currMsg) {
|
|
return
|
|
}
|
|
if (this.socket && selectedUser) {
|
|
const msg = {
|
|
msg: currMsg,
|
|
reciever: selectedUser,
|
|
sender: user,
|
|
date: new Date().getTime(),
|
|
unread: 1,
|
|
}
|
|
this.socket.emit('chat message', msg)
|
|
this.setState({
|
|
currMsg: '',
|
|
msgs: addMsgToGroup(msgs, msg, user),
|
|
})
|
|
}
|
|
}
|
|
|
|
partnerReadChatMessage(chatPartner) {
|
|
const { msgs } = this.state
|
|
this.setState({
|
|
msgs: {
|
|
...msgs,
|
|
[chatPartner]: msgs[chatPartner].map((msg) => {
|
|
if (msg.reciever === chatPartner) {
|
|
return {
|
|
...msg,
|
|
unread: 0,
|
|
}
|
|
} else {
|
|
return msg
|
|
}
|
|
}),
|
|
},
|
|
})
|
|
}
|
|
|
|
chatMessageRead(chatPartner) {
|
|
const { msgs, user } = this.state
|
|
if (this.props.refetchGlobalData) {
|
|
this.props.refetchGlobalData()
|
|
}
|
|
this.socket.emit('chat message read', { chatPartner: chatPartner })
|
|
this.setState({
|
|
msgs: {
|
|
...msgs,
|
|
[chatPartner]: msgs[chatPartner].map((msg) => {
|
|
if (msg.reciever === user) {
|
|
return {
|
|
...msg,
|
|
unread: 0,
|
|
}
|
|
} else {
|
|
return msg
|
|
}
|
|
}),
|
|
},
|
|
})
|
|
}
|
|
|
|
selectedUserChange(val) {
|
|
const { msgs, selectedUser, user } = this.state
|
|
const prevLastMessage = msgs[selectedUser]
|
|
? msgs[selectedUser][msgs[selectedUser].length - 1]
|
|
: null
|
|
if (
|
|
prevLastMessage &&
|
|
prevLastMessage.unread === 1 &&
|
|
prevLastMessage.sender !== user
|
|
) {
|
|
this.chatMessageRead(selectedUser)
|
|
}
|
|
this.setState({
|
|
selectedUser: val,
|
|
})
|
|
if (!val || isNaN(val)) {
|
|
return
|
|
}
|
|
const currSelectedMsgs = msgs[val]
|
|
if (!currSelectedMsgs) {
|
|
return
|
|
}
|
|
if (msgs[val].length <= 1) {
|
|
this.socket.emit('chat message open', {
|
|
chatPartner: val,
|
|
})
|
|
}
|
|
const lastMessage = currSelectedMsgs[currSelectedMsgs.length - 1]
|
|
if (lastMessage.unread === 1 && lastMessage.sender !== user) {
|
|
this.chatMessageRead(val)
|
|
}
|
|
}
|
|
|
|
renderChatInput() {
|
|
const { currMsg, msgs, selectedUser, user } = this.state
|
|
return (
|
|
<div className={styles.chatInput}>
|
|
<input
|
|
autoFocus
|
|
placeholder={'Message ...'}
|
|
type={'text'}
|
|
value={currMsg}
|
|
tabIndex={0}
|
|
onKeyUp={(e) => {
|
|
const lastMessage = msgs[selectedUser]
|
|
? msgs[selectedUser][msgs[selectedUser].length - 1]
|
|
: null
|
|
if (
|
|
lastMessage &&
|
|
lastMessage.unread === 1 &&
|
|
lastMessage.sender !== user
|
|
) {
|
|
this.chatMessageRead(selectedUser)
|
|
}
|
|
if (e.key === 'Enter') {
|
|
this.sendMsg()
|
|
}
|
|
}}
|
|
onChange={(e) => {
|
|
this.setState({
|
|
currMsg: e.target.value,
|
|
})
|
|
}}
|
|
/>
|
|
<div className={`buttonContainer ${styles.sendButton}`}>
|
|
<div
|
|
onClick={() => {
|
|
this.sendMsg()
|
|
}}
|
|
>
|
|
Send
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
renderHome() {
|
|
return (
|
|
<div className={styles.home}>
|
|
<div>
|
|
<input
|
|
autoFocus
|
|
type={'text'}
|
|
onChange={(e) => {
|
|
const val = parseInt(e.target.value)
|
|
if (!isNaN(val)) {
|
|
this.setState({
|
|
userInputVal: val,
|
|
})
|
|
}
|
|
}}
|
|
placeholder={'Címzett User ID-ja ...'}
|
|
/>
|
|
</div>
|
|
<div className={'buttonContainer'}>
|
|
<div
|
|
onClick={() => {
|
|
const { userInputVal } = this.state
|
|
if (isNaN(userInputVal) || userInputVal <= 0) {
|
|
alert('Érvényes User ID-t adj meg! (számot)')
|
|
return
|
|
}
|
|
this.setState({
|
|
selectedUser: userInputVal,
|
|
userInputVal: null,
|
|
})
|
|
}}
|
|
>
|
|
Chat!
|
|
</div>
|
|
</div>
|
|
<i>Admin User ID-ja: {'"1"'}</i>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
renderMsgs() {
|
|
const { msgs, selectedUser } = this.state
|
|
|
|
let prevMsg
|
|
return msgs[selectedUser].reduce((acc, msg, i) => {
|
|
if (prevMsg && prevMsg.unread === 0 && msg.unread === 1) {
|
|
if (msg.sender === selectedUser) {
|
|
acc.push(<NewMarker key={`marker_${i}`} />)
|
|
} else {
|
|
acc.push(<SeenMarker key={`marker_${i}`} />)
|
|
}
|
|
}
|
|
acc.push(this.renderMsg(msg, i))
|
|
prevMsg = msg
|
|
if (i === msgs[selectedUser].length - 1 && msg.unread === 0) {
|
|
if (msg.sender !== selectedUser) {
|
|
acc.push(<SeenMarker key={`marker_${i}`} />)
|
|
}
|
|
}
|
|
return acc
|
|
}, [])
|
|
}
|
|
|
|
renderMsg(message, key) {
|
|
const { date, sender, msg /* reciever */ } = message
|
|
const { user } = this.state
|
|
const timeString = new Date(date).toLocaleString()
|
|
|
|
return (
|
|
<div
|
|
key={key}
|
|
title={timeString}
|
|
className={`${styles.messageContainer} ${
|
|
sender === user ? styles.ownMsg : styles.partnerMsg
|
|
}`}
|
|
>
|
|
<div className={`${styles.messageEntry}`}>{msg}</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
renderSidebarEntryes() {
|
|
const { msgs } = this.state
|
|
const sorted = Object.keys(msgs).sort((a, b) => {
|
|
const lastA = msgs[a].slice(-1)[0]
|
|
const lastB = msgs[b].slice(-1)[0]
|
|
return lastB.date - lastA.date
|
|
})
|
|
return sorted.map((key, i) => {
|
|
return this.renderSidebarEntry(i, key)
|
|
})
|
|
}
|
|
|
|
renderSidebarEntry(i, key) {
|
|
const { selectedUser, msgs, user } = this.state
|
|
const group = msgs[key]
|
|
const lastMessage = group[group.length - 1]
|
|
|
|
return (
|
|
<div
|
|
className={`${styles.group} ${
|
|
lastMessage.unread === 1 && lastMessage.sender !== user
|
|
? styles.unread
|
|
: ''
|
|
} ${selectedUser === parseInt(key) ? styles.activeSidebarItem : ''}`}
|
|
onClick={() => {
|
|
this.selectedUserChange(parseInt(key))
|
|
}}
|
|
key={i}
|
|
>
|
|
<div>#{key}</div>
|
|
<div>{lastMessage.msg}</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
render() {
|
|
const { user, msgs, connected, selectedUser } = this.state
|
|
|
|
return (
|
|
<div className={styles.chat}>
|
|
<Head>
|
|
<title>{`Connected as ${user}`}</title>
|
|
</Head>
|
|
{connected ? (
|
|
<>
|
|
<div className={styles.main}>
|
|
<div className={styles.header}>
|
|
{selectedUser ? `User #${selectedUser}` : ''}
|
|
</div>
|
|
<div className={styles.messages} id={'messages'}>
|
|
{selectedUser === 0 ? this.renderHome() : null}
|
|
{selectedUser && msgs[selectedUser] ? this.renderMsgs() : null}
|
|
</div>
|
|
{selectedUser !== 0 ? this.renderChatInput() : null}
|
|
</div>
|
|
<div className={styles.sidebar}>
|
|
<div className={styles.usersContainer}>
|
|
<div
|
|
className={`${styles.group} ${
|
|
selectedUser === 0 ? styles.activeSidebarItem : ''
|
|
}`}
|
|
onClick={() => {
|
|
this.selectedUserChange(0)
|
|
}}
|
|
>
|
|
<div>Új beszélgetés</div>
|
|
</div>
|
|
<hr />
|
|
{msgs && this.renderSidebarEntryes()}
|
|
</div>
|
|
<span className={styles.spacer} />
|
|
<div></div>
|
|
</div>
|
|
</>
|
|
) : (
|
|
<div>
|
|
<LoadingIndicator />
|
|
<input
|
|
onChange={(e) => {
|
|
this.setState({
|
|
user: parseInt(e.target.value),
|
|
})
|
|
}}
|
|
type={'text'}
|
|
placeholder={'user'}
|
|
/>
|
|
<div
|
|
onClick={() => {
|
|
this.connect(this.state.user)
|
|
}}
|
|
>
|
|
connect
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|
|
}
|