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