Compare commits

...

3 Commits

Author SHA1 Message Date
Derek 22ef8bb29c Better fade out 2022-01-30 03:16:03 -07:00
Derek 14ec6550fa Emote support!!! 2022-01-30 03:15:54 -07:00
Derek 0083793ba3 Animations and MRO checking! 2022-01-30 02:18:15 -07:00
5 changed files with 82 additions and 26 deletions

View File

@ -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;
});
};

View File

@ -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]))
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>
{renderableEvents.map((event) => {
const Element = elementMap[event.type[0]];
return (
<Element key={event.data.id} {...event.data} />
);
})}
<List ref={listRef}>
{renderableEvents.map(([event, Element]) => (
<Element key={event.data.id} {...event.data} />
))}
</List>
);
}

View File

@ -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;
`;

View File

@ -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,
};

View File

@ -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;
`;