Update chat history

This commit is contained in:
Bill 2022-02-21 11:52:11 -05:00
parent 3ee74e2a04
commit fceabc0aaa
24 changed files with 249 additions and 288 deletions

View File

@ -0,0 +1,21 @@
import { createContext, FC, ProviderProps, useContext } from 'react';
import { IChatHistoryState } from './common/IChatHistoryState';
import { IRoomHistoryState } from './common/IRoomHistoryState';
export interface IChatHistoryContext
{
chatHistoryState: IChatHistoryState;
roomHistoryState: IRoomHistoryState;
}
const ChatHistoryContext = createContext<IChatHistoryContext>({
chatHistoryState: null,
roomHistoryState: null
});
export const ChatHistoryContextProvider: FC<ProviderProps<IChatHistoryContext>> = props =>
{
return <ChatHistoryContext.Provider value={ props.value }>{ props.children }</ChatHistoryContext.Provider>
}
export const useChatHistoryContext = () => useContext(ChatHistoryContext);

View File

@ -2,9 +2,14 @@ import { GetGuestRoomResultEvent, RoomSessionChatEvent, RoomSessionEvent } from
import { FC, useCallback, useState } from 'react'; import { FC, useCallback, useState } from 'react';
import { GetRoomSession } from '../../api'; import { GetRoomSession } from '../../api';
import { CreateMessageHook, useRoomSessionManagerEvent } from '../../hooks'; import { CreateMessageHook, useRoomSessionManagerEvent } from '../../hooks';
import { useChatHistoryContext } from './context/ChatHistoryContext'; import { useChatHistoryContext } from './ChatHistoryContext';
import { ChatEntryType, CHAT_HISTORY_MAX, IChatEntry, IRoomHistoryEntry, ROOM_HISTORY_MAX } from './context/ChatHistoryContext.types'; import { ChatEntryType } from './common/ChatEntryType';
import { currentDate } from './utils/Utilities'; import { IChatEntry } from './common/IChatEntry';
import { IRoomHistoryEntry } from './common/IRoomHistoryEntry';
import { currentDate } from './common/Utilities';
const CHAT_HISTORY_MAX = 1000;
const ROOM_HISTORY_MAX = 10;
export const ChatHistoryMessageHandler: FC<{}> = props => export const ChatHistoryMessageHandler: FC<{}> = props =>
{ {

View File

@ -0,0 +1,8 @@
.nitro-chat-history {
width: $chat-history-width;
height: $chat-history-height;
.content-area {
min-height: 200px;
}
}

View File

@ -0,0 +1,155 @@
import { ILinkEventTracker } from '@nitrots/nitro-renderer';
import { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { AutoSizer, CellMeasurer, CellMeasurerCache, List, ListRowProps, ListRowRenderer, Size } from 'react-virtualized';
import { RenderedRows } from 'react-virtualized/dist/es/List';
import { AddEventLinkTracker, LocalizeText, RemoveLinkEventTracker } from '../../api';
import { Flex, Text } from '../../common';
import { BatchUpdates } from '../../hooks';
import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../layout';
import { ChatHistoryContextProvider } from './ChatHistoryContext';
import { ChatHistoryMessageHandler } from './ChatHistoryMessageHandler';
import { ChatEntryType } from './common/ChatEntryType';
import { ChatHistoryState } from './common/ChatHistoryState';
import { SetChatHistory } from './common/GetChatHistory';
import { RoomHistoryState } from './common/RoomHistoryState';
export const ChatHistoryView: FC<{}> = props =>
{
const [ isVisible, setIsVisible ] = useState(false);
const [ needsScroll, setNeedsScroll ] = useState(false);
const [ chatHistoryUpdateId, setChatHistoryUpdateId ] = useState(-1);
const [ roomHistoryUpdateId, setRoomHistoryUpdateId ] = useState(-1);
const [ chatHistoryState, setChatHistoryState ] = useState(new ChatHistoryState());
const [ roomHistoryState, setRoomHistoryState ] = useState(new RoomHistoryState());
const elementRef = useRef<List>(null);
const cache = useMemo(() => new CellMeasurerCache({ defaultHeight: 25, fixedWidth: true }), []);
const RowRenderer: ListRowRenderer = (props: ListRowProps) =>
{
const item = chatHistoryState.chats[props.index];
const isDark = (props.index % 2 === 0);
return (
<CellMeasurer cache={ cache } columnIndex={ 0 } key={ props.key } parent={ props.parent } rowIndex={ props.index }>
<Flex key={ props.key } style={ props.style } className="p-1" gap={ 1 }>
<Text variant="muted">{ item.timestamp }</Text>
{ (item.type === ChatEntryType.TYPE_CHAT) &&
<>
<Text pointer noWrap dangerouslySetInnerHTML={ { __html: (item.name + ':') }} />
<Text textBreak wrap grow>{ item.message }</Text>
</> }
{ (item.type === ChatEntryType.TYPE_ROOM_INFO) &&
<>
<i className="icon icon-small-room" />
<Text textBreak wrap grow>{ item.name }</Text>
</> }
</Flex>
</CellMeasurer>
);
};
const onResize = (info: Size) => cache.clearAll();
const onRowsRendered = (info: RenderedRows) =>
{
if(elementRef && elementRef.current && isVisible && needsScroll)
{
elementRef.current.scrollToRow(chatHistoryState.chats.length);
setNeedsScroll(false);
}
}
const linkReceived = useCallback((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;
}
}, []);
useEffect(() =>
{
const linkTracker: ILinkEventTracker = {
linkReceived,
eventUrlPrefix: 'chat-history/'
};
AddEventLinkTracker(linkTracker);
return () => RemoveLinkEventTracker(linkTracker);
}, [ linkReceived ]);
useEffect(() =>
{
const chatState = new ChatHistoryState();
const roomState = new RoomHistoryState();
SetChatHistory(chatState);
chatState.notifier = () => setChatHistoryUpdateId(prevValue => (prevValue + 1));
roomState.notifier = () => setRoomHistoryUpdateId(prevValue => (prevValue + 1));
BatchUpdates(() =>
{
setChatHistoryState(chatState);
setRoomHistoryState(roomState);
});
return () =>
{
chatState.notifier = null;
roomState.notifier = null;
};
}, []);
useEffect(() =>
{
if(elementRef && elementRef.current && isVisible) elementRef.current.scrollToRow(chatHistoryState.chats.length);
setNeedsScroll(true);
}, [ isVisible, chatHistoryState.chats, chatHistoryUpdateId ]);
return (
<ChatHistoryContextProvider value={ { chatHistoryState, roomHistoryState } }>
<ChatHistoryMessageHandler />
{ isVisible &&
<NitroCardView uniqueKey="chat-history" className="nitro-chat-history">
<NitroCardHeaderView headerText={ LocalizeText('room.chathistory.button.text') } onCloseClick={ event => setIsVisible(false) }/>
<NitroCardContentView>
<AutoSizer defaultWidth={ 300 } defaultHeight={ 200 } onResize={ onResize }>
{ ({ height, width }) =>
{
return (
<List
ref={ elementRef }
width={ width }
height={ height }
rowCount={ chatHistoryState.chats.length }
rowHeight={ cache.rowHeight }
className={ 'chat-history-list' }
rowRenderer={ RowRenderer }
onRowsRendered={ onRowsRendered }
deferredMeasurementCache={ cache } />
)
} }
</AutoSizer>
</NitroCardContentView>
</NitroCardView> }
</ChatHistoryContextProvider>
);
}

View File

@ -0,0 +1,5 @@
export class ChatEntryType
{
public static TYPE_CHAT = 1;
public static TYPE_ROOM_INFO = 2;
}

View File

@ -1,4 +1,5 @@
import { IChatEntry, IChatHistoryState } from '../context/ChatHistoryContext.types'; import { IChatEntry } from './IChatEntry';
import { IChatHistoryState } from './IChatHistoryState';
export class ChatHistoryState implements IChatHistoryState export class ChatHistoryState implements IChatHistoryState
{ {
@ -10,6 +11,11 @@ export class ChatHistoryState implements IChatHistoryState
this._chats = []; this._chats = [];
} }
public notify(): void
{
if(this._notifier) this._notifier();
}
public get chats(): IChatEntry[] public get chats(): IChatEntry[]
{ {
return this._chats; return this._chats;
@ -24,9 +30,4 @@ export class ChatHistoryState implements IChatHistoryState
{ {
this._notifier = notifier; this._notifier = notifier;
} }
notify(): void
{
if(this._notifier) this._notifier();
}
} }

View File

@ -1,4 +1,4 @@
import { IChatHistoryState } from '../context/ChatHistoryContext.types'; import { IChatHistoryState } from './IChatHistoryState';
let GLOBAL_CHATS: IChatHistoryState = null; let GLOBAL_CHATS: IChatHistoryState = null;

View File

@ -0,0 +1,12 @@
export interface IChatEntry
{
id: number;
entityId: number;
name: string;
look?: string;
message?: string;
entityType?: number;
roomId: number;
timestamp: string;
type: number;
}

View File

@ -0,0 +1,8 @@
import { IChatEntry } from './IChatEntry';
export interface IChatHistoryState
{
chats: IChatEntry[];
notifier: () => void
notify(): void;
}

View File

@ -0,0 +1,4 @@
export interface IRoomHistoryEntry {
id: number;
name: string;
}

View File

@ -0,0 +1,8 @@
import { IRoomHistoryEntry } from './IRoomHistoryEntry';
export interface IRoomHistoryState
{
roomHistory: IRoomHistoryEntry[];
notifier: () => void;
notify: () => void;
}

View File

@ -1,4 +1,5 @@
import { IRoomHistoryEntry, IRoomHistoryState } from '../context/ChatHistoryContext.types'; import { IRoomHistoryEntry } from './IRoomHistoryEntry';
import { IRoomHistoryState } from './IRoomHistoryState';
export class RoomHistoryState implements IRoomHistoryState export class RoomHistoryState implements IRoomHistoryState
{ {

View File

@ -1,6 +1,7 @@
import { IChatEntry } from '../../../views/chat-history/context/ChatHistoryContext.types'; import { IChatEntry } from '../../chat-history/common/IChatEntry';
export interface IHelpReportState { export interface IHelpReportState
{
reportedUserId: number; reportedUserId: number;
reportedChats: IChatEntry[]; reportedChats: IChatEntry[];
cfhCategory: number; cfhCategory: number;

View File

@ -2,8 +2,9 @@ import { RoomObjectType } from '@nitrots/nitro-renderer';
import { FC, useMemo, useState } from 'react'; import { FC, useMemo, useState } from 'react';
import { LocalizeText } from '../../../api'; import { LocalizeText } from '../../../api';
import { Button, Column, Flex, Grid, LayoutGridItem, Text } from '../../../common'; import { Button, Column, Flex, Grid, LayoutGridItem, Text } from '../../../common';
import { GetChatHistory } from '../../../views/chat-history/common/GetChatHistory'; import { ChatEntryType } from '../../chat-history/common/ChatEntryType';
import { ChatEntryType, IChatEntry } from '../../../views/chat-history/context/ChatHistoryContext.types'; import { GetChatHistory } from '../../chat-history/common/GetChatHistory';
import { IChatEntry } from '../../chat-history/common/IChatEntry';
import { useHelpContext } from '../HelpContext'; import { useHelpContext } from '../HelpContext';
export const SelectReportedChatsView: FC<{}> = props => export const SelectReportedChatsView: FC<{}> = props =>

View File

@ -2,8 +2,8 @@ import { RoomObjectType } from '@nitrots/nitro-renderer';
import { FC, useMemo, useState } from 'react'; import { FC, useMemo, useState } from 'react';
import { GetSessionDataManager, LocalizeText } from '../../../api'; import { GetSessionDataManager, LocalizeText } from '../../../api';
import { Button, Column, Flex, Grid, LayoutGridItem, Text } from '../../../common'; import { Button, Column, Flex, Grid, LayoutGridItem, Text } from '../../../common';
import { GetChatHistory } from '../../../views/chat-history/common/GetChatHistory'; import { ChatEntryType } from '../../chat-history/common/ChatEntryType';
import { ChatEntryType } from '../../../views/chat-history/context/ChatHistoryContext.types'; import { GetChatHistory } from '../../chat-history/common/GetChatHistory';
import { IReportedUser } from '../common/IReportedUser'; import { IReportedUser } from '../common/IReportedUser';
import { useHelpContext } from '../HelpContext'; import { useHelpContext } from '../HelpContext';

View File

@ -2,6 +2,7 @@
@import './avatar-editor/AvatarEditorView'; @import './avatar-editor/AvatarEditorView';
@import './camera/CameraWidgetView'; @import './camera/CameraWidgetView';
@import './catalog/CatalogView'; @import './catalog/CatalogView';
@import './chat-history/ChatHistoryView';
@import './groups/GroupView'; @import './groups/GroupView';
@import './help/HelpView'; @import './help/HelpView';
@import './inventory/InventoryView'; @import './inventory/InventoryView';

View File

@ -1,8 +0,0 @@
import { NitroEvent } from '@nitrots/nitro-renderer';
export class ChatHistoryEvent extends NitroEvent
{
public static SHOW_CHAT_HISTORY: string = 'CHE_SHOW_CHAT_HISTORY';
public static HIDE_CHAT_HISTORY: string = 'CHE_HIDE_CHAT_HISTORY';
public static TOGGLE_CHAT_HISTORY: string = 'CHE_TOGGLE_CHAT_HISTORY';
}

View File

@ -8,7 +8,6 @@
@import "./right-side/RightSideView"; @import "./right-side/RightSideView";
@import "./room/RoomView"; @import "./room/RoomView";
@import "./room-host/RoomHostView"; @import "./room-host/RoomHostView";
@import "./chat-history/ChatHistoryView";
@import "./floorplan-editor/FloorplanEditorView"; @import "./floorplan-editor/FloorplanEditorView";
@import "./nitropedia/NitropediaView"; @import "./nitropedia/NitropediaView";
@import "./hc-center/HcCenterView.scss"; @import "./hc-center/HcCenterView.scss";

View File

@ -1,30 +0,0 @@
.nitro-chat-history {
width: $chat-history-width;
height: $chat-history-height;
background-color: #1C323F;
border: 2px solid rgba(255, 255, 255, 0.5);
.nitro-card-header-container {
background-color: #3d5f6e;
color: #fff;
}
.chat-history-content {
.chat-history-container {
min-height: 200px;
.chat-history-list {
.chathistory-entry {
.light {
background-color: #121f27;
}
.dark {
background-color: #0d171d;
}
}
}
}
}
}

View File

@ -1,167 +0,0 @@
import { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { AutoSizer, CellMeasurer, CellMeasurerCache, List, ListRowProps, ListRowRenderer, Size } from 'react-virtualized';
import { RenderedRows } from 'react-virtualized/dist/es/List';
import { ChatHistoryEvent } from '../../events/chat-history/ChatHistoryEvent';
import { useUiEvent } from '../../hooks';
import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../layout';
import { ChatHistoryMessageHandler } from './ChatHistoryMessageHandler';
import { ChatHistoryState } from './common/ChatHistoryState';
import { SetChatHistory } from './common/GetChatHistory';
import { RoomHistoryState } from './common/RoomHistoryState';
import { ChatHistoryContextProvider } from './context/ChatHistoryContext';
import { ChatEntryType } from './context/ChatHistoryContext.types';
export const ChatHistoryView: FC<{}> = props =>
{
const [ isVisible, setIsVisible ] = useState(false);
const [ needsScroll, setNeedsScroll ] = useState(false);
const [ chatHistoryUpdateId, setChatHistoryUpdateId ] = useState(-1);
const [ roomHistoryUpdateId, setRoomHistoryUpdateId ] = useState(-1);
const [ chatHistoryState, setChatHistoryState ] = useState(new ChatHistoryState());
const [ roomHistoryState, setRoomHistoryState ] = useState(new RoomHistoryState());
const elementRef = useRef<List>(null);
useEffect(() =>
{
const chatState = new ChatHistoryState();
const roomState = new RoomHistoryState();
SetChatHistory(chatState);
chatState.notifier = () => setChatHistoryUpdateId(prevValue => (prevValue + 1));
roomState.notifier = () => setRoomHistoryUpdateId(prevValue => (prevValue + 1));
setChatHistoryState(chatState);
setRoomHistoryState(roomState);
return () => {chatState.notifier = null; roomState.notifier = null;};
}, []);
const onChatHistoryEvent = useCallback((event: ChatHistoryEvent) =>
{
switch(event.type)
{
case ChatHistoryEvent.SHOW_CHAT_HISTORY:
setIsVisible(true);
break;
case ChatHistoryEvent.HIDE_CHAT_HISTORY:
setIsVisible(false);
break;
case ChatHistoryEvent.TOGGLE_CHAT_HISTORY:
setIsVisible(!isVisible);
break;
}
}, [isVisible]);
useUiEvent(ChatHistoryEvent.HIDE_CHAT_HISTORY, onChatHistoryEvent);
useUiEvent(ChatHistoryEvent.SHOW_CHAT_HISTORY, onChatHistoryEvent);
useUiEvent(ChatHistoryEvent.TOGGLE_CHAT_HISTORY, onChatHistoryEvent);
const cache = useMemo(() =>
{
return new CellMeasurerCache({
defaultHeight: 25,
fixedWidth: true,
//keyMapper: (index) => chatHistoryState.chats[index].id
});
}, []);
const RowRenderer: ListRowRenderer = (props: ListRowProps) =>
{
const item = chatHistoryState.chats[props.index];
const isDark = (props.index % 2 === 0);
return (
<CellMeasurer
cache={cache}
columnIndex={0}
key={props.key}
parent={props.parent}
rowIndex={props.index}
>
<div key={props.key} style={props.style} className="chathistory-entry justify-content-start">
{(item.type === ChatEntryType.TYPE_CHAT) &&
<div className={`p-1 d-flex gap-1 ${isDark ? 'dark' : 'light'}`}>
<div className="text-muted">{item.timestamp}</div>
<div className="cursor-pointer d-flex text-nowrap" dangerouslySetInnerHTML={ { __html: (item.name + ':') }} />
<div className="text-break text-wrap flex-grow-1">{item.message}</div>
</div>
}
{(item.type === ChatEntryType.TYPE_ROOM_INFO) &&
<div className={`p-1 d-flex gap-1 ${isDark ? 'dark' : 'light'}`}>
<div className="text-muted">{item.timestamp}</div>
<i className="icon icon-small-room" />
<div className="cursor-pointer text-break text-wrap">{item.name}</div>
</div>
}
</div>
</CellMeasurer>
);
};
const onResize = useCallback((info: Size) =>
{
cache.clearAll();
}, [cache]);
const onRowsRendered = useCallback((info: RenderedRows) =>
{
if(elementRef && elementRef.current && isVisible && needsScroll)
{
console.log('stop ' + info.stopIndex);
//if(chatHistoryState.chats.length > 0) elementRef.current.measureAllRows();
elementRef.current.scrollToRow(chatHistoryState.chats.length);
console.log('scroll')
setNeedsScroll(false);
}
}, [chatHistoryState.chats.length, isVisible, needsScroll]);
useEffect(() =>
{
if(elementRef && elementRef.current && isVisible)
{
//if(chatHistoryState.chats.length > 0) elementRef.current.measureAllRows();
elementRef.current.scrollToRow(chatHistoryState.chats.length);
}
//console.log(chatHistoryState.chats.length);
setNeedsScroll(true);
}, [chatHistoryState.chats, isVisible, chatHistoryUpdateId]);
return (
<ChatHistoryContextProvider value={ { chatHistoryState, roomHistoryState } }>
<ChatHistoryMessageHandler />
{isVisible &&
<NitroCardView uniqueKey="chat-history" className="nitro-chat-history rounded" simple={ false } theme={'dark'} >
<NitroCardHeaderView headerText={ 'Chat History' } onCloseClick={ event => setIsVisible(false) } theme={'dark'}/>
<NitroCardContentView className="chat-history-content p-0" theme={'dark'}>
<div className="row w-100 h-100 chat-history-container">
<AutoSizer defaultWidth={300} defaultHeight={200} onResize={onResize}>
{({ height, width }) =>
{
return (
<List
ref={elementRef}
width={width}
height={height}
rowCount={chatHistoryState.chats.length}
rowHeight={cache.rowHeight}
className={'chat-history-list'}
rowRenderer={RowRenderer}
onRowsRendered={onRowsRendered}
deferredMeasurementCache={cache}
/>
)
}
}
</AutoSizer>
</div>
</NitroCardContentView>
</NitroCardView>
}
</ChatHistoryContextProvider>
);
}

View File

@ -1,14 +0,0 @@
import { createContext, FC, useContext } from 'react';
import { ChatHistoryContextProps, IChatHistoryContext } from './ChatHistoryContext.types';
const ChatHistoryContext = createContext<IChatHistoryContext>({
chatHistoryState: null,
roomHistoryState: null
});
export const ChatHistoryContextProvider: FC<ChatHistoryContextProps> = props =>
{
return <ChatHistoryContext.Provider value={ props.value }>{ props.children }</ChatHistoryContext.Provider>
}
export const useChatHistoryContext = () => useContext(ChatHistoryContext);

View File

@ -1,50 +0,0 @@
import { ProviderProps } from 'react';
export interface IChatHistoryContext
{
chatHistoryState: IChatHistoryState;
roomHistoryState: IRoomHistoryState;
}
export interface IChatHistoryState {
chats: IChatEntry[];
notifier: () => void
notify(): void;
}
export interface IRoomHistoryState {
roomHistory: IRoomHistoryEntry[];
notifier: () => void
notify(): void;
}
export interface IChatEntry {
id: number;
entityId: number;
name: string;
look?: string;
message?: string;
entityType?: number;
roomId: number;
timestamp: string;
type: number;
}
export interface IRoomHistoryEntry {
id: number;
name: string;
}
export class ChatEntryType
{
public static TYPE_CHAT = 1;
public static TYPE_ROOM_INFO = 2;
}
export const CHAT_HISTORY_MAX = 1000;
export const ROOM_HISTORY_MAX = 10;
export interface ChatHistoryContextProps extends ProviderProps<IChatHistoryContext>
{
}

View File

@ -5,6 +5,7 @@ import { AchievementsView } from '../../components/achievements/AchievementsView
import { AvatarEditorView } from '../../components/avatar-editor/AvatarEditorView'; import { AvatarEditorView } from '../../components/avatar-editor/AvatarEditorView';
import { CameraWidgetView } from '../../components/camera/CameraWidgetView'; import { CameraWidgetView } from '../../components/camera/CameraWidgetView';
import { CatalogView } from '../../components/catalog/CatalogView'; import { CatalogView } from '../../components/catalog/CatalogView';
import { ChatHistoryView } from '../../components/chat-history/ChatHistoryView';
import { GroupsView } from '../../components/groups/GroupsView'; import { GroupsView } from '../../components/groups/GroupsView';
import { HelpView } from '../../components/help/HelpView'; import { HelpView } from '../../components/help/HelpView';
import { InventoryView } from '../../components/inventory/InventoryView'; import { InventoryView } from '../../components/inventory/InventoryView';
@ -17,7 +18,6 @@ import { WiredView } from '../../components/wired/WiredView';
import { useRoomSessionManagerEvent } from '../../hooks/events/nitro/session/room-session-manager-event'; import { useRoomSessionManagerEvent } from '../../hooks/events/nitro/session/room-session-manager-event';
import { TransitionAnimation, TransitionAnimationTypes } from '../../layout'; import { TransitionAnimation, TransitionAnimationTypes } from '../../layout';
import { CampaignView } from '../campaign/CampaignView'; import { CampaignView } from '../campaign/CampaignView';
import { ChatHistoryView } from '../chat-history/ChatHistoryView';
import { FloorplanEditorView } from '../floorplan-editor/FloorplanEditorView'; import { FloorplanEditorView } from '../floorplan-editor/FloorplanEditorView';
import { FriendsView } from '../friends/FriendsView'; import { FriendsView } from '../friends/FriendsView';
import { HcCenterView } from '../hc-center/HcCenterView'; import { HcCenterView } from '../hc-center/HcCenterView';