Merge branch '@experimental/freeflowchat' into 'dev'

@experimental/freeflowchat

See merge request nitro/nitro-react!38
This commit is contained in:
Bill 2022-03-27 04:39:07 +00:00
commit d3fd557ed3
7 changed files with 136 additions and 62 deletions

View File

@ -1,10 +0,0 @@
export const DoElementsOverlap = (a: HTMLElement, b: HTMLElement) =>
{
const rectA = a.getBoundingClientRect();
const rectB = b.getBoundingClientRect();
const ox = Math.abs(rectA.x - rectB.x) < (rectA.x < rectB.x ? rectB.width : rectA.width);
const oy = Math.abs(rectA.y - rectB.y) < (rectA.y < rectB.y ? rectB.height : rectA.height);
return (ox && oy);
}

View File

@ -1,6 +1,5 @@
export * from './CloneObject';
export * from './ColorUtils';
export * from './DoElementsOverlap';
export * from './LocalizeBadgeDescription';
export * from './LocalizeBageName';
export * from './LocalizeFormattedNumber';

View File

@ -28,9 +28,9 @@ export const NavigatorRoomSettingsVipChatTabView: FC<NavigatorRoomSettingsTabVie
<option value="1">{LocalizeText('navigator.roomsettings.chat.mode.line.by.line')}</option>
</select>
<select className="form-select form-select-sm" value={roomSettingsData.chatBubbleWeight} onChange={event => handleChange('chat_weight', event.target.value)}>
<option value="0">{LocalizeText('navigator.roomsettings.chat.bubbles.width.normal')}</option>
<option value="1">{LocalizeText('navigator.roomsettings.chat.bubbles.width.thin')}</option>
<option value="2">{LocalizeText('navigator.roomsettings.chat.bubbles.width.wide')}</option>
<option value="1">{LocalizeText('navigator.roomsettings.chat.bubbles.width.normal')}</option>
<option value="2">{LocalizeText('navigator.roomsettings.chat.bubbles.width.thin')}</option>
<option value="0">{LocalizeText('navigator.roomsettings.chat.bubbles.width.wide')}</option>
</select>
<select className="form-select form-select-sm" value={roomSettingsData.chatBubbleSpeed} onChange={event => handleChange('bubble_speed', event.target.value)}>
<option value="0">{LocalizeText('navigator.roomsettings.chat.speed.fast')}</option>

View File

@ -1,4 +1,5 @@
import { FC, MouseEvent, useEffect, useRef, useState } from 'react';
import { RoomChatSettings } from '@nitrots/nitro-renderer';
import { FC, useEffect, useMemo, useRef, useState } from 'react';
import { ChatBubbleMessage } from './common/ChatBubbleMessage';
interface ChatWidgetMessageViewProps
@ -6,15 +7,27 @@ interface ChatWidgetMessageViewProps
chat: ChatBubbleMessage;
makeRoom: (chat: ChatBubbleMessage) => void;
onChatClicked: (chat: ChatBubbleMessage) => void;
bubbleWidth?: number;
}
export const ChatWidgetMessageView: FC<ChatWidgetMessageViewProps> = props =>
{
const { chat = null, makeRoom = null, onChatClicked = null } = props;
const { chat = null, makeRoom = null, onChatClicked = null, bubbleWidth = RoomChatSettings.CHAT_BUBBLE_WIDTH_NORMAL } = props;
const [ isVisible, setIsVisible ] = useState(false);
const elementRef = useRef<HTMLDivElement>();
const onMouseDown = (event: MouseEvent<HTMLDivElement>) => onChatClicked(chat);
const getBubbleWidth = useMemo(() =>
{
switch(bubbleWidth)
{
case RoomChatSettings.CHAT_BUBBLE_WIDTH_NORMAL:
return 350;
case RoomChatSettings.CHAT_BUBBLE_WIDTH_THIN:
return 240;
case RoomChatSettings.CHAT_BUBBLE_WIDTH_WIDE:
return 2000;
}
}, [ bubbleWidth ]);
useEffect(() =>
{
@ -57,17 +70,19 @@ export const ChatWidgetMessageView: FC<ChatWidgetMessageViewProps> = props =>
useEffect(() => setIsVisible(chat.visible), [ chat.visible ]);
return (
<div ref={ elementRef } className="bubble-container" style={ { visibility: (isVisible ? 'visible' : 'hidden') } } onClick={ onMouseDown }>
{ (chat.styleId === 0) && <div className="user-container-bg" style={ { backgroundColor: chat.color } } /> }
<div className={ 'chat-bubble bubble-' + chat.styleId + ' type-' + chat.type }>
<div ref={ elementRef } className={ `bubble-container ${ isVisible ? 'visible' : 'invisible' }` } onClick={ event => onChatClicked(chat) }>
{ (chat.styleId === 0) &&
<div className="user-container-bg" style={ { backgroundColor: chat.color } } /> }
<div className={ `chat-bubble bubble-${ chat.styleId } type-${ chat.type }` } style={ { maxWidth: getBubbleWidth } }>
<div className="user-container">
{ (chat.imageUrl && (chat.imageUrl !== '')) && <div className="user-image" style={ { backgroundImage: 'url(' + chat.imageUrl + ')' } } /> }
{ chat.imageUrl && (chat.imageUrl.length > 0) &&
<div className="user-image" style={ { backgroundImage: `url(${ chat.imageUrl })` } } /> }
</div>
<div className="chat-content">
<b className="username mr-1" dangerouslySetInnerHTML={ { __html: `${ chat.username }: ` } } />
<span className="message" dangerouslySetInnerHTML={{ __html: `${ chat.formattedText }` }} />
</div>
<div className="pointer"></div>
<div className="pointer" />
</div>
</div>
);

View File

@ -5,7 +5,7 @@
align-items: center;
width: 100%;
top: 0;
height: 270px;
min-height: 1px;
z-index: $chat-zindex;
background-color: transparent;
border-radius: 0;

View File

@ -1,13 +1,15 @@
import { NitroPoint, RoomDragEvent } from '@nitrots/nitro-renderer';
import { GetGuestRoomResultEvent, NitroPoint, RoomChatSettings, RoomChatSettingsEvent, RoomDragEvent } from '@nitrots/nitro-renderer';
import { FC, useCallback, useEffect, useRef, useState } from 'react';
import { RoomChatFormatter, RoomWidgetChatSelectAvatarMessage, RoomWidgetRoomObjectMessage, RoomWidgetUpdateChatEvent } from '../../../../api';
import { UseEventDispatcherHook, UseRoomEngineEvent } from '../../../../hooks';
import { GetConfiguration, RoomChatFormatter, RoomWidgetChatSelectAvatarMessage, RoomWidgetRoomObjectMessage, RoomWidgetUpdateChatEvent } from '../../../../api';
import { UseEventDispatcherHook, UseMessageEventHook, UseRoomEngineEvent } from '../../../../hooks';
import { useRoomContext } from '../../RoomContext';
import { ChatWidgetMessageView } from './ChatWidgetMessageView';
import { ChatBubbleMessage } from './common/ChatBubbleMessage';
import { DoChatsOverlap } from './common/DoChatsOverlap';
export const ChatWidgetView: FC<{}> = props =>
{
const [chatSettings, setChatSettings] = useState<RoomChatSettings>(null);
const [ chatMessages, setChatMessages ] = useState<ChatBubbleMessage[]>([]);
const { roomSession = null, eventDispatcher = null, widgetHandler = null } = useRoomContext();
const elementRef = useRef<HTMLDivElement>();
@ -21,40 +23,66 @@ export const ChatWidgetView: FC<{}> = props =>
if(newMessages.length !== chatMessages.length) setChatMessages(newMessages);
}, [ chatMessages ]);
const moveChatUp = useCallback((chat: ChatBubbleMessage, amount: number) =>
{
chat.top -= amount;
}, []);
const moveAllChatsUp = useCallback((amount: number) =>
{
chatMessages.forEach(chat => moveChatUp(chat, amount));
chatMessages.forEach(chat => (chat.top -= amount));
removeHiddenChats();
}, [ chatMessages, moveChatUp, removeHiddenChats ]);
}, [ chatMessages, removeHiddenChats ]);
const checkOverlappingChats = useCallback((chat: ChatBubbleMessage, moved: number, tempChats: ChatBubbleMessage[]) =>
{
const totalChats = chatMessages.length;
if(!totalChats) return;
for(let i = (totalChats - 1); i >= 0; i--)
{
const collides = chatMessages[i];
if(!collides || (chat === collides) || (tempChats.indexOf(collides) >= 0) || ((collides.top - moved) >= (chat.top + chat.height))) continue;
if(DoChatsOverlap(chat, collides, -moved))
{
const amount = Math.abs((collides.top + collides.height) - chat.top);
tempChats.push(collides);
collides.top -= amount;
checkOverlappingChats(collides, amount, tempChats);
}
}
}, [ chatMessages ]);
const makeRoom = useCallback((chat: ChatBubbleMessage) =>
{
const lowestPoint = ((chat.top + chat.height) - 1);
const requiredSpace = (chat.height + 1);
const spaceAvailable = (elementRef.current.offsetHeight - lowestPoint);
if(spaceAvailable < requiredSpace)
if(chatSettings.mode === RoomChatSettings.CHAT_MODE_FREE_FLOW)
{
const amount = (requiredSpace - spaceAvailable);
chatMessages.forEach(existingChat =>
{
if(existingChat === chat) return;
moveChatUp(existingChat, amount);
});
checkOverlappingChats(chat, 0, [ chat ]);
removeHiddenChats();
}
}, [ chatMessages, moveChatUp, removeHiddenChats ]);
else
{
const lowestPoint = (chat.top + chat.height);
const requiredSpace = chat.height;
const spaceAvailable = (elementRef.current.offsetHeight - lowestPoint);
const amount = (requiredSpace - spaceAvailable);
const addChat = useCallback((chat: ChatBubbleMessage) => setChatMessages(prevValue => [ ...prevValue, chat ]), []);
if(spaceAvailable < requiredSpace)
{
chatMessages.forEach(existingChat =>
{
if(existingChat === chat) return;
existingChat.top -= amount;
});
removeHiddenChats();
}
}
}, [ chatSettings, chatMessages, removeHiddenChats, checkOverlappingChats ]);
const onRoomWidgetUpdateChatEvent = useCallback((event: RoomWidgetUpdateChatEvent) =>
{
@ -71,23 +99,16 @@ export const ChatWidgetView: FC<{}> = props =>
event.userImage,
(event.userColor && (('#' + (event.userColor.toString(16).padStart(6, '0'))) || null)));
addChat(chatMessage);
}, [ addChat ]);
setChatMessages(prevValue => [ ...prevValue, chatMessage ]);
}, []);
UseEventDispatcherHook(RoomWidgetUpdateChatEvent.CHAT_EVENT, eventDispatcher, onRoomWidgetUpdateChatEvent);
const onRoomDragEvent = useCallback((event: RoomDragEvent) =>
{
if(!chatMessages.length) return;
if(!chatMessages.length || (event.roomId !== roomSession.roomId)) return;
if(event.roomId !== roomSession.roomId) return;
chatMessages.forEach(chat =>
{
if(!chat.elementRef) return;
chat.left += event.offsetX;
});
chatMessages.forEach(chat => (chat.elementRef && (chat.left += event.offsetX)));
}, [ roomSession, chatMessages ]);
UseRoomEngineEvent(RoomDragEvent.ROOM_DRAG, onRoomDragEvent);
@ -98,19 +119,61 @@ export const ChatWidgetView: FC<{}> = props =>
widgetHandler.processWidgetMessage(new RoomWidgetChatSelectAvatarMessage(RoomWidgetChatSelectAvatarMessage.MESSAGE_SELECT_AVATAR, chat.senderId, chat.username, chat.roomId));
}, [ widgetHandler ]);
const getScrollSpeed = useCallback(() =>
{
if(!chatSettings) return 6000;
switch(chatSettings.speed)
{
case RoomChatSettings.CHAT_SCROLL_SPEED_FAST:
return 3000;
case RoomChatSettings.CHAT_SCROLL_SPEED_NORMAL:
return 6000;
case RoomChatSettings.CHAT_SCROLL_SPEED_SLOW:
return 12000;
}
}, [ chatSettings ])
const onGetGuestRoomResultEvent = useCallback((event: GetGuestRoomResultEvent) =>
{
const parser = event.getParser();
if(!parser.roomEnter) return;
setChatSettings(parser.chat);
}, []);
UseMessageEventHook(GetGuestRoomResultEvent, onGetGuestRoomResultEvent);
const onRoomChatSettingsEvent = useCallback((event: RoomChatSettingsEvent) =>
{
const parser = event.getParser();
setChatSettings(parser.chat);
}, []);
UseMessageEventHook(RoomChatSettingsEvent, onRoomChatSettingsEvent);
useEffect(() =>
{
const interval = setInterval(() => moveAllChatsUp(15), 4500);
const interval = setInterval(() => moveAllChatsUp(15), getScrollSpeed());
return () =>
{
if(interval) clearInterval(interval);
}
}, [ chatMessages, moveAllChatsUp ]);
}, [ chatMessages, moveAllChatsUp, getScrollSpeed ]);
useEffect(() =>
{
if(!elementRef || !elementRef.current) return;
elementRef.current.style.height = ((document.body.offsetHeight * GetConfiguration<number>('chat.viewer.height.percentage')) + 'px');
}, []);
return (
<div ref={ elementRef } className="nitro-chat-widget">
{ chatMessages.map(chat => <ChatWidgetMessageView key={ chat.id } chat={ chat } makeRoom={ makeRoom } onChatClicked={ onChatClicked } />) }
{chatMessages.map(chat => <ChatWidgetMessageView key={chat.id} chat={chat} makeRoom={makeRoom} onChatClicked={onChatClicked} bubbleWidth={ chatSettings.weight }/>)}
</div>
);
}

View File

@ -0,0 +1,7 @@
import { ChatBubbleMessage } from './ChatBubbleMessage';
export const DoChatsOverlap = (a: ChatBubbleMessage, b: ChatBubbleMessage, additionalBTop: number) =>
{
return !(((a.left + a.width) < b.left) || (a.left > (b.left + b.width)) || ((a.top + a.height) < (b.top + additionalBTop)) || (a.top > ((b.top + additionalBTop) + b.height)));
}