mirror of
https://github.com/duckietm/Nitro-V3.git
synced 2026-06-19 15:06:20 +00:00
160 lines
7.7 KiB
TypeScript
160 lines
7.7 KiB
TypeScript
import { AddLinkEventTracker, ILinkEventTracker, RemoveLinkEventTracker } from '@nitrots/nitro-renderer';
|
|
import { FC, useEffect, useMemo, useRef, useState } from 'react';
|
|
import { ChatEntryType, LocalizeText } from '../../api';
|
|
import { Flex, NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView, Text } from '../../common';
|
|
import { useChatHistory, useMentionsSnapshot, useOnClickChat } from '../../hooks';
|
|
import { useUserDataSnapshot } from '../../hooks/session/useSessionSnapshots';
|
|
import { NitroInput } from '../../layout';
|
|
import { MentionRowView, useMentionActions } from '../mentions';
|
|
|
|
const TAB_CHAT = 'chat';
|
|
const TAB_MENTIONS = 'mentions';
|
|
|
|
export const ChatHistoryView: FC<{}> = props =>
|
|
{
|
|
const [isVisible, setIsVisible] = useState(false);
|
|
const [searchText, setSearchText] = useState<string>('');
|
|
const [activeTab, setActiveTab] = useState<string>(TAB_CHAT);
|
|
const { chatHistory = [] } = useChatHistory();
|
|
const { mentions, unreadCount } = useMentionsSnapshot();
|
|
const { userName: ownMentionUsername = '' } = useUserDataSnapshot();
|
|
const { open: onMentionOpen, goto: onMentionGoto, remove: onMentionRemove } = useMentionActions();
|
|
const { onClickChat } = useOnClickChat();
|
|
const elementRef = useRef<HTMLDivElement>(null);
|
|
const isFirstRender = useRef(true);
|
|
const prevChatLength = useRef<number>(0);
|
|
|
|
const filteredChatHistory = useMemo(() =>
|
|
{
|
|
let result = chatHistory;
|
|
|
|
if (searchText.length > 0)
|
|
{
|
|
const text = searchText.toLowerCase();
|
|
result = chatHistory.filter(entry =>
|
|
(entry.message && entry.message.toLowerCase().includes(text)) ||
|
|
(entry.name && entry.name.toLowerCase().includes(text))
|
|
);
|
|
}
|
|
|
|
return [...result];
|
|
}, [chatHistory, searchText]);
|
|
|
|
useEffect(() =>
|
|
{
|
|
if (!elementRef.current || !isVisible) return;
|
|
|
|
const element = elementRef.current;
|
|
const maxScrollTop = Math.max(0, element.scrollHeight - element.clientHeight);
|
|
const isAtBottom = maxScrollTop === 0 || Math.abs(element.scrollTop - maxScrollTop) <= 50;
|
|
|
|
if (isFirstRender.current)
|
|
{
|
|
element.scrollTo({ top: element.scrollHeight, behavior: 'smooth' });
|
|
isFirstRender.current = false;
|
|
}
|
|
else if (filteredChatHistory.length > prevChatLength.current)
|
|
{
|
|
element.scrollTo({ top: element.scrollHeight, behavior: 'smooth' });
|
|
}
|
|
|
|
prevChatLength.current = filteredChatHistory.length;
|
|
}, [filteredChatHistory, isVisible]);
|
|
|
|
useEffect(() =>
|
|
{
|
|
const linkTracker: ILinkEventTracker = {
|
|
linkReceived: (url: string) =>
|
|
{
|
|
const parts = url.split('/');
|
|
|
|
if (parts.length < 2) return;
|
|
|
|
switch (parts[1])
|
|
{
|
|
case 'show':
|
|
setIsVisible(true);
|
|
return;
|
|
case 'hide':
|
|
setIsVisible(false);
|
|
return;
|
|
case 'toggle':
|
|
setIsVisible(prevValue => !prevValue);
|
|
return;
|
|
}
|
|
},
|
|
eventUrlPrefix: 'chat-history/'
|
|
};
|
|
|
|
AddLinkEventTracker(linkTracker);
|
|
|
|
return () => RemoveLinkEventTracker(linkTracker);
|
|
}, []);
|
|
|
|
if (!isVisible) return null;
|
|
|
|
return (
|
|
<NitroCardView className="w-[400px] h-[400px] bg-[#f0f0f0]" theme="primary-slim" uniqueKey="chat-history">
|
|
<NitroCardHeaderView headerText={LocalizeText('room.chathistory.button.text')} onCloseClick={event => setIsVisible(false)} />
|
|
<NitroCardTabsView>
|
|
<NitroCardTabsItemView isActive={ activeTab === TAB_CHAT } onClick={ () => setActiveTab(TAB_CHAT) }>
|
|
{ LocalizeText('room.chathistory.button.text') }
|
|
</NitroCardTabsItemView>
|
|
<NitroCardTabsItemView count={ unreadCount } isActive={ activeTab === TAB_MENTIONS } onClick={ () => setActiveTab(TAB_MENTIONS) }>
|
|
{ LocalizeText('mentions.tab.title') }
|
|
</NitroCardTabsItemView>
|
|
</NitroCardTabsView>
|
|
<NitroCardContentView className="h-full bg-[#f0f0f0] bg-[url('@/assets/images/chat/chathistory_background.png')] bg-repeat bg-auto" gap={2} overflow="hidden" style={{ height: 'calc(100% - 40px)', display: 'flex', flexDirection: 'column' }}>
|
|
{ activeTab === TAB_MENTIONS ? (
|
|
<div style={{ flex: 1, overflowY: 'auto', background: 'inherit' }}>
|
|
{ (mentions.length === 0)
|
|
? <Text center variant="gray">{ LocalizeText('mentions.window.empty') }</Text>
|
|
: mentions.map(mention => (
|
|
<MentionRowView
|
|
key={ mention.mentionId }
|
|
mention={ mention }
|
|
onGoto={ onMentionGoto }
|
|
onOpen={ onMentionOpen }
|
|
onRemove={ onMentionRemove }
|
|
ownUsername={ ownMentionUsername } />
|
|
)) }
|
|
</div>
|
|
) : (
|
|
<>
|
|
<NitroInput placeholder={LocalizeText('generic.search')} type="text" value={searchText} onChange={event => setSearchText(event.target.value)} />
|
|
<div ref={elementRef} style={{ flex: 1, overflowY: 'auto', background: 'inherit' }}>
|
|
{filteredChatHistory.map((row, index) => (
|
|
<Flex key={index} alignItems="center" className="p-1" gap={2}>
|
|
<Text variant="gray">{row.timestamp}</Text>
|
|
{row.type === ChatEntryType.TYPE_CHAT && (
|
|
<div className="bubble-container" style={{ position: 'relative', display: 'inline-flex', alignItems: 'center' }}>
|
|
<div
|
|
className={`chat-bubble bubble-${row.style} type-${row.chatType}`}
|
|
style={{ maxWidth: '100%', backgroundColor: row.style === 0 ? row.color : 'transparent', position: 'relative', zIndex: 1 }}>
|
|
<div className="user-container">
|
|
{row.imageUrl && row.imageUrl.length > 0 && (
|
|
<div className="user-image" style={{ backgroundImage: `url(${row.imageUrl})` }} />
|
|
)}
|
|
</div>
|
|
<div className="chat-content">
|
|
<b className="mr-1 username" dangerouslySetInnerHTML={{ __html: `${row.name}: ` }} />
|
|
<span className="message" dangerouslySetInnerHTML={{ __html: `${row.message}` }} onClick={ onClickChat } />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
{row.type === ChatEntryType.TYPE_ROOM_INFO && (
|
|
<>
|
|
<i className="nitro-icon icon-small-room" />
|
|
<Text grow textBreak wrap>{row.name}</Text>
|
|
</>
|
|
)}
|
|
</Flex>
|
|
))}
|
|
</div>
|
|
</>
|
|
) }
|
|
</NitroCardContentView>
|
|
</NitroCardView>
|
|
);
|
|
}; |