Few changes to chat.

Changed the way the background is set on self sent messages and some
styling.

Fixed chat container not scrolling. Added 'go to bottom' button.
This commit is contained in:
t1enne 2022-07-01 19:35:14 +02:00
parent 43aba0a67c
commit 455d8f8169
4 changed files with 87 additions and 47 deletions

View File

@ -1,8 +1,16 @@
.chatHeader { .chatHeader {
text-align: center; text-align: center;
font-weight: bold;
padding: 5px 0; padding: 5px 0;
color: var(--text-color-secondary); color: var(--text-color-secondary);
border-bottom: 1px solid var(--color-owncast-gray-700); border-bottom: 1px solid var(--color-owncast-gray-700);
font-variant: small-caps; font-variant: small-caps;
} }
.toBottomWrap {
display: flex;
width: 100%;
justify-content: center;
position: absolute;
bottom: 5px;
}

View File

@ -1,8 +1,7 @@
import { Spin } from 'antd'; import { Button, Spin } from 'antd';
import { Virtuoso } from 'react-virtuoso'; import { Virtuoso } from 'react-virtuoso';
import { useMemo, useRef } from 'react'; import { useState, useMemo, useRef } from 'react';
import { LoadingOutlined } from '@ant-design/icons'; import { LoadingOutlined, VerticalAlignBottomOutlined } from '@ant-design/icons';
import { MessageType, NameChangeEvent } from '../../../interfaces/socket-events'; import { MessageType, NameChangeEvent } from '../../../interfaces/socket-events';
import s from './ChatContainer.module.scss'; import s from './ChatContainer.module.scss';
import { ChatMessage } from '../../../interfaces/chat-message.model'; import { ChatMessage } from '../../../interfaces/chat-message.model';
@ -19,6 +18,8 @@ interface Props {
export default function ChatContainer(props: Props) { export default function ChatContainer(props: Props) {
const { messages, loading, usernameToHighlight, chatUserId, isModerator } = props; const { messages, loading, usernameToHighlight, chatUserId, isModerator } = props;
const [atBottom, setAtBottom] = useState(false);
// const [showButton, setShowButton] = useState(false);
const chatContainerRef = useRef(null); const chatContainerRef = useRef(null);
const spinIcon = <LoadingOutlined style={{ fontSize: '32px' }} spin />; const spinIcon = <LoadingOutlined style={{ fontSize: '32px' }} spin />;
@ -34,12 +35,12 @@ export default function ChatContainer(props: Props) {
); );
}; };
const getViewForMessage = message => { const getViewForMessage = (message: ChatMessage | NameChangeEvent) => {
switch (message.type) { switch (message.type) {
case MessageType.CHAT: case MessageType.CHAT:
return ( return (
<ChatUserMessage <ChatUserMessage
message={message} message={message as ChatMessage}
showModeratorMenu={isModerator} // Moderators have access to an additional menu showModeratorMenu={isModerator} // Moderators have access to an additional menu
highlightString={usernameToHighlight} // What to highlight in the message highlightString={usernameToHighlight} // What to highlight in the message
renderAsPersonallySent={message.user?.id === chatUserId} // The local user sent this message renderAsPersonallySent={message.user?.id === chatUserId} // The local user sent this message
@ -47,7 +48,7 @@ export default function ChatContainer(props: Props) {
/> />
); );
case MessageType.NAME_CHANGE: case MessageType.NAME_CHANGE:
return getNameChangeViewForMessage(message); return getNameChangeViewForMessage(message as NameChangeEvent);
default: default:
return null; return null;
} }
@ -55,17 +56,36 @@ export default function ChatContainer(props: Props) {
const MessagesTable = useMemo( const MessagesTable = useMemo(
() => ( () => (
<Virtuoso <>
style={{ height: '70vh' }} <Virtuoso
ref={chatContainerRef} style={{ height: '75vh' }}
initialTopMostItemIndex={999} // Force alignment to bottom ref={chatContainerRef}
data={messages} initialTopMostItemIndex={messages.length - 1} // Force alignment to bottom
itemContent={(index, message) => getViewForMessage(message)} data={messages}
followOutput="auto" itemContent={(_, message) => getViewForMessage(message)}
alignToBottom followOutput="auto"
/> alignToBottom
atBottomStateChange={bottom => setAtBottom(bottom)}
/>
{!atBottom && (
<div className={s.toBottomWrap}>
<Button
ghost
icon={<VerticalAlignBottomOutlined />}
onClick={() =>
chatContainerRef.current.scrollToIndex({
index: messages.length - 1,
behavior: 'smooth',
})
}
>
Go to last message
</Button>
</div>
)}
</>
), ),
[messages, usernameToHighlight, chatUserId, isModerator], [messages, usernameToHighlight, chatUserId, isModerator, atBottom],
); );
return ( return (

View File

@ -1,9 +1,20 @@
.root { .root {
position: relative; position: relative;
font-size: 0.9rem; font-size: 0.9rem;
padding: 5px; padding: 5px 15px 5px 5px;
padding-left: 1rem; padding-left: 1rem;
margin: 8px 5px; // animation: chatFadeIn .1s ease-in;
.background {
position: absolute;
top: 0px;
left: 0px;
width: 100%;
height: 100%;
background-color: currentColor;
opacity: 0.07;
border-radius: .25rem;
overflow: hidden;
}
.user { .user {
font: var(--theme-header-font-family); font: var(--theme-header-font-family);
color: var(--color-owncast-grey-100); color: var(--color-owncast-grey-100);
@ -56,3 +67,14 @@
} }
} }
} }
@keyframes chatFadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}

View File

@ -20,7 +20,6 @@ export default function ChatUserMessage({
showModeratorMenu, showModeratorMenu,
renderAsPersonallySent, // Move the border to the right and render a background renderAsPersonallySent, // Move the border to the right and render a background
}: Props) { }: Props) {
const [bgColor, setBgColor] = useState<string>();
const { body, user, timestamp } = message; const { body, user, timestamp } = message;
const { displayName, displayColor } = user; const { displayName, displayColor } = user;
@ -28,38 +27,29 @@ export default function ChatUserMessage({
const formattedTimestamp = `Sent at ${formatTimestamp(timestamp)}`; const formattedTimestamp = `Sent at ${formatTimestamp(timestamp)}`;
const [formattedMessage, setFormattedMessage] = useState<string>(body); const [formattedMessage, setFormattedMessage] = useState<string>(body);
useEffect(() => {
if (renderAsPersonallySent) setBgColor(getBgColor(displayColor));
}, []);
useEffect(() => { useEffect(() => {
setFormattedMessage(he.decode(body)); setFormattedMessage(he.decode(body));
}, [message]); }, [message]);
return ( return (
<div <div style={{ padding: 5 }}>
className={cn(s.root, { <div
[s.ownMessage]: renderAsPersonallySent, className={cn(s.root, {
})} [s.ownMessage]: renderAsPersonallySent,
data-display-color={displayColor} })}
style={{ borderColor: color, backgroundColor: bgColor }} style={{ borderColor: color }}
title={formattedTimestamp} title={formattedTimestamp}
> >
<div className={s.user} style={{ color }}> <div className={s.user} style={{ color }}>
{displayName} {displayName}
</div>
<Highlight search={highlightString}>
<div className={s.message}>{formattedMessage}</div>
</Highlight>
{showModeratorMenu && <div>Moderator menu</div>}
<div className={s.customBorder} style={{ color }} />
<div className={s.background} style={{ color }} />
</div> </div>
<Highlight search={highlightString}>
<div className={s.message}>{formattedMessage}</div>
</Highlight>
{showModeratorMenu && <div>Moderator menu</div>}
<div className={s.customBorder} style={{ color }} />
</div> </div>
); );
} }
function getBgColor(displayColor: number, alpha = 13) {
const root = document.querySelector(':root');
const css = getComputedStyle(root);
const hexColor = css.getPropertyValue(`--theme-user-colors-${displayColor}`);
return hexColor.concat(alpha.toString());
}