Compare commits
3 Commits
85870d1f7d
...
22ef8bb29c
Author | SHA1 | Date |
---|---|---|
Derek | 22ef8bb29c | |
Derek | 14ec6550fa | |
Derek | 0083793ba3 |
|
@ -2,19 +2,19 @@ import { useEffect, useState, useRef } from 'react';
|
|||
|
||||
import ChatLog from '../chat-log';
|
||||
|
||||
const maxsize = 100;
|
||||
const maxsize = 25;
|
||||
|
||||
function App() {
|
||||
const [messages, setMessages] = useState([]);
|
||||
const [messages, setMessages] = useState(Array(maxsize).fill(null));
|
||||
const socket = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
const onEvent = function (event) {
|
||||
setMessages((state) => {
|
||||
if (state.length >= maxsize) {
|
||||
state.pop();
|
||||
}
|
||||
return [JSON.parse(event.data), ...state];
|
||||
const newState = [...state];
|
||||
newState.unshift(JSON.parse(event.data));
|
||||
newState.pop();
|
||||
return newState;
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -1,26 +1,36 @@
|
|||
import { useRef, useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import Message from './message';
|
||||
|
||||
import { List } from './chat-log.style';
|
||||
import { List, Spacer } from './chat-log.style';
|
||||
|
||||
const elementMap = {
|
||||
Message,
|
||||
null: Spacer,
|
||||
};
|
||||
|
||||
function ChatLog({
|
||||
log,
|
||||
}) {
|
||||
// TODO: Should look at full MRO
|
||||
const renderableEvents = log.filter((event) => Object.keys(elementMap).includes(event.type[0]))
|
||||
return (
|
||||
<List>
|
||||
{renderableEvents.map((event) => {
|
||||
const Element = elementMap[event.type[0]];
|
||||
const listRef = useRef();
|
||||
const renderableEvents = log.map((event) => {
|
||||
if (!event?.type) {
|
||||
event = { type: ['null'], data: { }};
|
||||
}
|
||||
const bestRenderableInterface = event.type.find((interfaceName) => Object.keys(elementMap).includes(interfaceName));
|
||||
const renderer = elementMap[bestRenderableInterface];
|
||||
return [event, renderer];
|
||||
}).filter(([event, renderer]) => !!renderer);
|
||||
|
||||
useEffect(() => {
|
||||
listRef.current.scrollTop = -1;
|
||||
}, [renderableEvents]);
|
||||
|
||||
return (
|
||||
<List ref={listRef}>
|
||||
{renderableEvents.map(([event, Element]) => (
|
||||
<Element key={event.data.id} {...event.data} />
|
||||
);
|
||||
})}
|
||||
))}
|
||||
</List>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ import styled from 'styled-components';
|
|||
|
||||
export const List = styled.div`
|
||||
height: 100%;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column-reverse;
|
||||
align-items: start;
|
||||
|
@ -9,4 +10,17 @@ export const List = styled.div`
|
|||
gap: 16px;
|
||||
|
||||
padding: 12px 8px;
|
||||
|
||||
overflow: scroll;
|
||||
scroll-behavior: smooth;
|
||||
|
||||
scrollbar-width: none;
|
||||
&::-webkit-scrollbar {
|
||||
width: 0px;
|
||||
background: transparent;
|
||||
}
|
||||
`;
|
||||
|
||||
export const Spacer = styled.div`
|
||||
min-height: 2em;
|
||||
`;
|
||||
|
|
|
@ -2,7 +2,7 @@ import PropTypes from 'prop-types';
|
|||
|
||||
import { XTERM_COLORS } from '../../../index.style';
|
||||
|
||||
import { Wrapper, Author } from './message.style';
|
||||
import { Wrapper, Author, Body, Emote } from './message.style';
|
||||
|
||||
const stringHashcode = (str) => {
|
||||
var hash = 0, i, chr;
|
||||
|
@ -15,17 +15,29 @@ const stringHashcode = (str) => {
|
|||
return hash;
|
||||
}
|
||||
|
||||
const emoteRegex = /:([^:\s]+):/g;
|
||||
|
||||
function Message({
|
||||
user_id, user_name, user_type, user_color, text,
|
||||
user_id, user_name, user_type, user_color, text, emotes,
|
||||
deemphasize,
|
||||
}) {
|
||||
const fallbackColor = XTERM_COLORS[(stringHashcode(user_id.toString()) % 144) + 88];
|
||||
|
||||
return (
|
||||
<Wrapper type={user_type} color={user_color ?? fallbackColor} deemphasize={deemphasize}>
|
||||
<Author>
|
||||
[ {user_name} ]
|
||||
</Author>
|
||||
{text}
|
||||
<Body>
|
||||
{text.split(emoteRegex).map((textOrEmote, index) => {
|
||||
if (textOrEmote === "") return null;
|
||||
if (index % 2 === 0) {
|
||||
return <span>{textOrEmote}</span>;
|
||||
} else {
|
||||
return <Emote src={emotes[textOrEmote]} />;
|
||||
}
|
||||
}).filter((item) => item !== null)}
|
||||
</Body>
|
||||
</Wrapper>
|
||||
);
|
||||
}
|
||||
|
@ -35,12 +47,16 @@ Message.propTypes = {
|
|||
user_name: PropTypes.node.isRequired,
|
||||
user_type: PropTypes.string.isRequired,
|
||||
user_color: PropTypes.string,
|
||||
deemphasize: PropTypes.bool,
|
||||
text: PropTypes.node.isRequired,
|
||||
emotes: PropTypes.shape({
|
||||
[PropTypes.string]: PropTypes.string,
|
||||
}),
|
||||
deemphasize: PropTypes.bool,
|
||||
};
|
||||
|
||||
Message.defaultProps = {
|
||||
user_color: undefined,
|
||||
emotes: {},
|
||||
deemphasize: false,
|
||||
};
|
||||
|
||||
|
|
|
@ -7,6 +7,16 @@ export const Author = styled.div`
|
|||
margin-bottom: 4px;
|
||||
`;
|
||||
|
||||
export const Body = styled.span`
|
||||
`;
|
||||
|
||||
export const Emote = styled.img`
|
||||
width: 2em;
|
||||
height: 2em;
|
||||
vertical-align: middle;
|
||||
`;
|
||||
|
||||
|
||||
export const normalizeColor = (color) => {
|
||||
if (color === null) {
|
||||
return 'inherit';
|
||||
|
@ -63,18 +73,24 @@ export const Wrapper = styled.div`
|
|||
width: 100%;
|
||||
overflow-wrap: break-word;
|
||||
|
||||
@keyframes dissapear {
|
||||
@keyframes fade {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
15% {
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
animation: 160s dissapear linear;
|
||||
@keyframes slide {
|
||||
from {
|
||||
transform: translateX(-50px);
|
||||
}
|
||||
to {
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
animation: 160s fade cubic-bezier(.6,.04,.98,.34), 0.3s slide cubic-bezier(.08,.82,.17,1);
|
||||
animation-fill-mode: forwards;
|
||||
`;
|
||||
|
|
Loading…
Reference in New Issue