feat: initial messages
This commit is contained in:
parent
b9b439290e
commit
7982eb054a
|
@ -2,9 +2,13 @@ import './chat--center.scss';
|
|||
|
||||
import React from 'react';
|
||||
|
||||
import ChatMessages from './chat--messages';
|
||||
|
||||
export default function ChatCenter() {
|
||||
return (
|
||||
<div className="chat__center flexed" />
|
||||
<div className="center flexed">
|
||||
<ChatMessages />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
.chat__center {
|
||||
.center {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
|
56
src/client/chat--message.jsx
Normal file
56
src/client/chat--message.jsx
Normal file
|
@ -0,0 +1,56 @@
|
|||
import './chat--message.scss';
|
||||
|
||||
import classnames from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
|
||||
export default function PlayersChatMessageSpace(props) {
|
||||
const {message: {owner, text, timestamp}, isShort} = props;
|
||||
const ownerMap = {
|
||||
1: 'cha0s',
|
||||
2: 'BabeHasHiccups',
|
||||
};
|
||||
const dtf = new Intl.DateTimeFormat(undefined, {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
});
|
||||
const date = new Date(timestamp);
|
||||
const $messageTime = (
|
||||
<div
|
||||
className="chat--messageTime"
|
||||
title={date.toISOString()}
|
||||
>
|
||||
{dtf.format(date)}
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div
|
||||
className={classnames('chat--message', {short: isShort})}
|
||||
>
|
||||
{
|
||||
!isShort && (
|
||||
<header>
|
||||
<div className="chat--messageOwner">{ownerMap[owner]}</div>
|
||||
{$messageTime}
|
||||
</header>
|
||||
)
|
||||
}
|
||||
<div className="chat--messageText">
|
||||
<div className="chat--messageMarkdown">
|
||||
<ReactMarkdown source={text} />
|
||||
</div>
|
||||
{isShort && $messageTime}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
PlayersChatMessageSpace.propTypes = {
|
||||
isShort: PropTypes.bool.isRequired,
|
||||
message: PropTypes.shape({
|
||||
text: PropTypes.string,
|
||||
timestamp: PropTypes.number,
|
||||
owner: PropTypes.string,
|
||||
}).isRequired,
|
||||
};
|
82
src/client/chat--message.scss
Normal file
82
src/client/chat--message.scss
Normal file
|
@ -0,0 +1,82 @@
|
|||
.chat--message {
|
||||
margin-top: 0.75rem;
|
||||
padding: 0.25rem 1rem;
|
||||
}
|
||||
|
||||
.chat--message, .chat--message * {
|
||||
font-family: 'Lucida Sans', 'Lucida Sans Regular', 'Lucida Grande', 'Lucida Sans Unicode', Geneva, Verdana, sans-serif;
|
||||
}
|
||||
|
||||
.chat--message:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.chat--message.short {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.chat--message.short .chat--messageTime {
|
||||
display: inline;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.chat--message.short:hover .chat--messageTime {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.chat--message.short .chat--messageOwner {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.chat--message:hover {
|
||||
background-color: #1c1c1c;
|
||||
}
|
||||
|
||||
header {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.chat--messageOwner {
|
||||
color: #d68030aa;
|
||||
font-family: Caladea, 'Times New Roman', Times, serif;
|
||||
font-weight: bold;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.chat--messageTime {
|
||||
color: #666;
|
||||
font-size: 0.7rem;
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
.chat--message:not(.short) .chat--messageTime {
|
||||
line-height: 1.25rem;
|
||||
}
|
||||
|
||||
.chat--messageText {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.chat--messageMarkdown * {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.chat--messageMarkdown {
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
line-height: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.chat--messageMarkdown {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.chat--messageMarkdown :first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.chat--messageMarkdown :last-child {
|
||||
display: inline;
|
||||
}
|
103
src/client/chat--messages.jsx
Normal file
103
src/client/chat--messages.jsx
Normal file
|
@ -0,0 +1,103 @@
|
|||
import './chat--messages.scss';
|
||||
|
||||
import React, {useLayoutEffect, useRef, useState} from 'react';
|
||||
|
||||
import ChatMessage from './chat--message';
|
||||
|
||||
export default function PlayersChatSpace() {
|
||||
const [$form, $messages] = [useRef(null), useRef(null)];
|
||||
const [text, setText] = useState('');
|
||||
const messages = [
|
||||
{
|
||||
key: 1,
|
||||
owner: 1,
|
||||
text: 'Hi!',
|
||||
timestamp: Date.now(),
|
||||
},
|
||||
{
|
||||
key: 2,
|
||||
owner: 2,
|
||||
text: 'Yo.',
|
||||
timestamp: Date.now(),
|
||||
},
|
||||
{
|
||||
key: 3,
|
||||
owner: 2,
|
||||
text: 'How have you been?',
|
||||
timestamp: Date.now(),
|
||||
},
|
||||
{
|
||||
key: 4,
|
||||
owner: 1,
|
||||
text: 'Not too bad.',
|
||||
timestamp: Date.now(),
|
||||
},
|
||||
];
|
||||
const {current} = $messages;
|
||||
const isAtTheBottom = !current
|
||||
? true
|
||||
: 0 === current.scrollTop || (
|
||||
current.scrollHeight
|
||||
=== current.scrollTop
|
||||
+ ['margin-top', 'margin-bottom'].reduce((r, property) => (
|
||||
r + parseInt(window.getComputedStyle(current).getPropertyValue(property), 10)
|
||||
), current.offsetHeight)
|
||||
);
|
||||
const heightWatch = current && current.scrollHeight;
|
||||
const messageCount = messages && messages.length;
|
||||
useLayoutEffect(() => {
|
||||
if (isAtTheBottom) {
|
||||
current?.scrollTo(0, !current ? 0 : current.scrollHeight);
|
||||
}
|
||||
}, [current, heightWatch, messageCount, isAtTheBottom]);
|
||||
let messageOwner = false;
|
||||
return (
|
||||
<div className="chat--messages">
|
||||
<div className="chat--messagesSmoosh" />
|
||||
<div
|
||||
className="chat--messagesList"
|
||||
ref={$messages}
|
||||
>
|
||||
{messages && messages.map((message) => {
|
||||
const $message = (
|
||||
<ChatMessage
|
||||
key={message.key}
|
||||
isShort={messageOwner === message.owner}
|
||||
message={message}
|
||||
/>
|
||||
);
|
||||
messageOwner = message.owner;
|
||||
return $message;
|
||||
})}
|
||||
</div>
|
||||
<form
|
||||
onSubmit={(event) => {
|
||||
event.preventDefault();
|
||||
const trimmed = text.slice(0, 1000).trim();
|
||||
if (trimmed) {
|
||||
// socket.send(new ActionPacket(chat({owner: playerId, text: trimmed}, {game: id})));
|
||||
}
|
||||
setText('');
|
||||
}}
|
||||
ref={$form}
|
||||
>
|
||||
<textarea
|
||||
className="chat--messagesTextarea"
|
||||
name="message"
|
||||
type="textarea"
|
||||
maxLength="1000"
|
||||
onChange={(event) => {
|
||||
setText(event.target.value);
|
||||
}}
|
||||
onKeyDown={(event) => {
|
||||
if ('Enter' === event.key && !event.shiftKey) {
|
||||
$form.current?.dispatchEvent(new Event('submit', {cancelable: true}));
|
||||
event.preventDefault();
|
||||
}
|
||||
}}
|
||||
value={text}
|
||||
/>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
28
src/client/chat--messages.scss
Normal file
28
src/client/chat--messages.scss
Normal file
|
@ -0,0 +1,28 @@
|
|||
.chat--messages {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.chat--messagesSmoosh {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.chat--messagesList {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.chat--messagesTextarea {
|
||||
background-color: #333;
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
color: #aaa;
|
||||
font-family: 'Lucida Sans', 'Lucida Sans Regular', 'Lucida Grande', 'Lucida Sans Unicode', Geneva, Verdana, sans-serif;
|
||||
font-size: 0.9rem;
|
||||
height: 4rem;
|
||||
margin: 0.5rem 1rem 1rem;
|
||||
padding: 0.5rem;
|
||||
resize: none;
|
||||
width: calc(100% - 2rem);
|
||||
}
|
Loading…
Reference in New Issue
Block a user