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:
@@ -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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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());
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user