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'; import ChatLog from '../chat-log';
const maxsize = 100; const maxsize = 25;
function App() { function App() {
const [messages, setMessages] = useState([]); const [messages, setMessages] = useState(Array(maxsize).fill(null));
const socket = useRef(null); const socket = useRef(null);
useEffect(() => { useEffect(() => {
const onEvent = function (event) { const onEvent = function (event) {
setMessages((state) => { setMessages((state) => {
if (state.length >= maxsize) { const newState = [...state];
state.pop(); newState.unshift(JSON.parse(event.data));
} newState.pop();
return [JSON.parse(event.data), ...state]; return newState;
}); });
}; };

View File

@ -1,26 +1,36 @@
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 } from './chat-log.style'; import { List, Spacer } from './chat-log.style';
const elementMap = { const elementMap = {
Message, Message,
null: Spacer,
}; };
function ChatLog({ function ChatLog({
log, log,
}) { }) {
// TODO: Should look at full MRO const listRef = useRef();
const renderableEvents = log.filter((event) => Object.keys(elementMap).includes(event.type[0])) const renderableEvents = log.map((event) => {
return ( if (!event?.type) {
<List> event = { type: ['null'], data: { }};
{renderableEvents.map((event) => { }
const Element = elementMap[event.type[0]]; 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 ( return (
<List ref={listRef}>
{renderableEvents.map(([event, Element]) => (
<Element key={event.data.id} {...event.data} /> <Element key={event.data.id} {...event.data} />
); ))}
})}
</List> </List>
); );
} }

View File

@ -2,6 +2,7 @@ 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;
@ -9,4 +10,17 @@ 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;
`; `;

View File

@ -2,7 +2,7 @@ import PropTypes from 'prop-types';
import { XTERM_COLORS } from '../../../index.style'; import { XTERM_COLORS } from '../../../index.style';
import { Wrapper, Author } from './message.style'; import { Wrapper, Author, Body, Emote } from './message.style';
const stringHashcode = (str) => { const stringHashcode = (str) => {
var hash = 0, i, chr; var hash = 0, i, chr;
@ -15,17 +15,29 @@ const stringHashcode = (str) => {
return hash; return hash;
} }
const emoteRegex = /:([^:\s]+):/g;
function Message({ function Message({
user_id, user_name, user_type, user_color, text, user_id, user_name, user_type, user_color, text, emotes,
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>
{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> </Wrapper>
); );
} }
@ -35,12 +47,16 @@ 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,
deemphasize: PropTypes.bool,
text: PropTypes.node.isRequired, text: PropTypes.node.isRequired,
emotes: PropTypes.shape({
[PropTypes.string]: PropTypes.string,
}),
deemphasize: PropTypes.bool,
}; };
Message.defaultProps = { Message.defaultProps = {
user_color: undefined, user_color: undefined,
emotes: {},
deemphasize: false, deemphasize: false,
}; };

View File

@ -7,6 +7,16 @@ 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';
@ -63,18 +73,24 @@ export const Wrapper = styled.div`
width: 100%; width: 100%;
overflow-wrap: break-word; overflow-wrap: break-word;
@keyframes dissapear { @keyframes fade {
0% { 0% {
opacity: 1; opacity: 1;
} }
15% {
opacity: 1;
}
100% { 100% {
opacity: 0; 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; animation-fill-mode: forwards;
`; `;