1
0
James Ketrenos 99f0971873 Lots of fixes for volcano and other features
Signed-off-by: James Ketrenos <james_eikona@ketrenos.com>
2022-06-18 12:30:26 -07:00

201 lines
6.3 KiB
JavaScript

import React, { useState, useEffect, useContext, useRef, useCallback, useMemo } from "react";
import Paper from '@material-ui/core/Paper';
import List from '@material-ui/core/List';
import ListItem from '@material-ui/core/ListItem';
import ListItemText from '@material-ui/core/ListItemText';
import Moment from 'react-moment';
import TextField from '@material-ui/core/TextField';
import 'moment-timezone';
import "./Chat.css";
import { PlayerColor } from './PlayerColor.js';
import { Resource } from './Resource.js';
import { Dice } from './Dice.js';
import { GlobalContext } from "./GlobalContext.js";
const Chat = () => {
const [lastTop, setLastTop] = useState(0);
const [autoScroll, setAutoScroll] = useState(true);
const [latest, setLatest] = useState('');
const [scrollTime, setScrollTime] = useState(0);
const [chat, setChat] = useState([]);
const [startTime, setStartTime] = useState(0);
const { ws, name } = useContext(GlobalContext);
const fields = useMemo(() => [
'chat', 'startTime'
], []);
const onWsMessage = (event) => {
const data = JSON.parse(event.data);
switch (data.type) {
case 'game-update':
console.log(`chat - game update`);
if (data.update.chat && data.update.chat.length !== chat.length) {
console.log(`chat - game update - ${data.update.chat.length} lines`);
setChat(data.update.chat);
}
if (data.update.startTime && data.update.startTime !== startTime) {
setStartTime(data.update.startTime);
}
break;
default:
break;
}
};
const refWsMessage = useRef(onWsMessage);
useEffect(() => { refWsMessage.current = onWsMessage; });
useEffect(() => {
if (!ws) { return; }
const cbMessage = e => refWsMessage.current(e);
ws.addEventListener('message', cbMessage);
return () => {
ws.removeEventListener('message', cbMessage);
}
}, [ws, refWsMessage]);
useEffect(() => {
if (!ws) { return; }
ws.send(JSON.stringify({
type: 'get',
fields
}));
}, [ws, fields]);
const chatKeyPress = useCallback((event) => {
if (event.key === "Enter") {
if (!autoScroll) {
setAutoScroll(true);
}
ws.send(JSON.stringify({ type: 'chat', message: event.target.value }));
event.target.value = "";
}
}, [ws, setAutoScroll, autoScroll]);
const chatScroll = (event) => {
const chatList = event.target,
fromBottom = Math.round(Math.abs((chatList.scrollHeight - chatList.offsetHeight) - chatList.scrollTop));
/* If scroll is within 20 pixels of the bottom, turn on auto-scroll */
const shouldAutoscroll = (fromBottom < 20);
if (shouldAutoscroll !== autoScroll) {
setAutoScroll(shouldAutoscroll);
}
/* If the list should not auto scroll, then cache the current
* top of the list and record when we did this so we honor
* the auto-scroll for at least 500ms */
if (!shouldAutoscroll) {
const target = Math.round(chatList.scrollTop);
if (target !== lastTop) {
setLastTop(target);
setScrollTime(Date.now());
}
}
};
useEffect(() => {
const chatList = document.getElementById("ChatList"),
currentTop = Math.round(chatList.scrollTop);
if (autoScroll) {
/* Auto-scroll to the bottom of the chat window */
const target = Math.round(chatList.scrollHeight - chatList.offsetHeight);
if (currentTop !== target) {
chatList.scrollTop = target;
}
return;
}
/* Maintain current position in scrolled view if the user hasn't
* been scrolling in the past 0.5s */
if ((Date.now() - scrollTime) > 500 && currentTop !== lastTop) {
chatList.scrollTop = lastTop;
}
});
const messages = chat.map((item, index) => {
let message;
/* Do not perform extra parsing on player-generated
* messages */
if (item.normalChat) {
message = <div key={`line-${index}`}>{item.message}</div>;
} else {
const punctuation = item.message.match(/(\.+$)/);
let period;
if (punctuation) {
period = punctuation[1];
} else {
period = '';
}
let lines = item.message.split('.');
message = lines
.filter(line => line.trim() !== '')
.map((line, index) => {
/* If the date is in the future, set it to now */
const dice = line.match(/^(.*rolled )([1-6])(, ([1-6]))?(.*)$/);
if (dice) {
if (dice[4]) {
return <div key={`line-${index}`}>{dice[1]}
<Dice pips={dice[2]}/>,
<Dice pips={dice[4]}/>{dice[5]}{ period }</div>;
} else {
return <div key={`line-${index}`}>{dice[1]}
<Dice pips={dice[2]}/>{dice[5]}{ period }</div>;
}
}
let start = line, message;
while (start) {
const resource = start.match(/^(.*)(([0-9]+) (wood|sheep|wheat|stone|brick),?)(.*)$/);
if (resource) {
const count = resource[3] ? parseInt(resource[3]) : 1;
message = <><Resource label={true} count={count}
type={resource[4]} disabled/>{resource[5]}{message}</>;
start = resource[1];
} else {
message = <>{start}{message}</>;
start = '';
}
}
return <div key={`line-${index}`}>{ message }{ period }</div>;
});
}
return (
<ListItem key={`msg-${item.date}`}
className={item.color ? '' : 'System'}>
{ item.color &&
<PlayerColor color={item.color}/>
}
<ListItemText primary={message}
secondary={item.color && <Moment fromNow trim date={item.date > Date.now() ?
Date.now() : item.date} interval={1000}/>} />
</ListItem>
);
});
if (chat.length && chat[chat.length - 1].date !== latest) {
setLatest(chat[chat.length - 1].date);
setAutoScroll(true);
}
return (
<Paper className="Chat">
<List className="ChatList" id="ChatList" onScroll={chatScroll}>
{ messages }
</List>
<TextField className="ChatInput"
disabled={!name}
onKeyPress={chatKeyPress}
label={startTime !== 0 && <>Game duration: <Moment tz={"Etc/GMT"}
format="h:mm:ss"
trim
durationFromNow interval={1000}
date={startTime}/></>}
variant="outlined"/>
</Paper>
);
}
export { Chat };