mirror of
https://github.com/billsonnn/nitro-react.git
synced 2025-01-19 05:46:27 +01:00
Merge branch '@experimental/freeflowchat' into 'dev'
@experimental/freeflowchat See merge request nitro/nitro-react!38
This commit is contained in:
commit
d3fd557ed3
@ -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);
|
|
||||||
}
|
|
@ -1,6 +1,5 @@
|
|||||||
export * from './CloneObject';
|
export * from './CloneObject';
|
||||||
export * from './ColorUtils';
|
export * from './ColorUtils';
|
||||||
export * from './DoElementsOverlap';
|
|
||||||
export * from './LocalizeBadgeDescription';
|
export * from './LocalizeBadgeDescription';
|
||||||
export * from './LocalizeBageName';
|
export * from './LocalizeBageName';
|
||||||
export * from './LocalizeFormattedNumber';
|
export * from './LocalizeFormattedNumber';
|
||||||
|
@ -28,9 +28,9 @@ export const NavigatorRoomSettingsVipChatTabView: FC<NavigatorRoomSettingsTabVie
|
|||||||
<option value="1">{LocalizeText('navigator.roomsettings.chat.mode.line.by.line')}</option>
|
<option value="1">{LocalizeText('navigator.roomsettings.chat.mode.line.by.line')}</option>
|
||||||
</select>
|
</select>
|
||||||
<select className="form-select form-select-sm" value={roomSettingsData.chatBubbleWeight} onChange={event => handleChange('chat_weight', event.target.value)}>
|
<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.normal')}</option>
|
||||||
<option value="1">{LocalizeText('navigator.roomsettings.chat.bubbles.width.thin')}</option>
|
<option value="2">{LocalizeText('navigator.roomsettings.chat.bubbles.width.thin')}</option>
|
||||||
<option value="2">{LocalizeText('navigator.roomsettings.chat.bubbles.width.wide')}</option>
|
<option value="0">{LocalizeText('navigator.roomsettings.chat.bubbles.width.wide')}</option>
|
||||||
</select>
|
</select>
|
||||||
<select className="form-select form-select-sm" value={roomSettingsData.chatBubbleSpeed} onChange={event => handleChange('bubble_speed', event.target.value)}>
|
<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>
|
<option value="0">{LocalizeText('navigator.roomsettings.chat.speed.fast')}</option>
|
||||||
|
@ -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';
|
import { ChatBubbleMessage } from './common/ChatBubbleMessage';
|
||||||
|
|
||||||
interface ChatWidgetMessageViewProps
|
interface ChatWidgetMessageViewProps
|
||||||
@ -6,15 +7,27 @@ interface ChatWidgetMessageViewProps
|
|||||||
chat: ChatBubbleMessage;
|
chat: ChatBubbleMessage;
|
||||||
makeRoom: (chat: ChatBubbleMessage) => void;
|
makeRoom: (chat: ChatBubbleMessage) => void;
|
||||||
onChatClicked: (chat: ChatBubbleMessage) => void;
|
onChatClicked: (chat: ChatBubbleMessage) => void;
|
||||||
|
bubbleWidth?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ChatWidgetMessageView: FC<ChatWidgetMessageViewProps> = props =>
|
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 [ isVisible, setIsVisible ] = useState(false);
|
||||||
const elementRef = useRef<HTMLDivElement>();
|
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(() =>
|
useEffect(() =>
|
||||||
{
|
{
|
||||||
@ -57,17 +70,19 @@ export const ChatWidgetMessageView: FC<ChatWidgetMessageViewProps> = props =>
|
|||||||
useEffect(() => setIsVisible(chat.visible), [ chat.visible ]);
|
useEffect(() => setIsVisible(chat.visible), [ chat.visible ]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={ elementRef } className="bubble-container" style={ { visibility: (isVisible ? 'visible' : 'hidden') } } onClick={ onMouseDown }>
|
<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 } } /> }
|
{ (chat.styleId === 0) &&
|
||||||
<div className={ 'chat-bubble bubble-' + chat.styleId + ' type-' + chat.type }>
|
<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">
|
<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>
|
||||||
<div className="chat-content">
|
<div className="chat-content">
|
||||||
<b className="username mr-1" dangerouslySetInnerHTML={ { __html: `${ chat.username }: ` } } />
|
<b className="username mr-1" dangerouslySetInnerHTML={ { __html: `${ chat.username }: ` } } />
|
||||||
<span className="message" dangerouslySetInnerHTML={{ __html: `${ chat.formattedText }` }} />
|
<span className="message" dangerouslySetInnerHTML={{ __html: `${ chat.formattedText }` }} />
|
||||||
</div>
|
</div>
|
||||||
<div className="pointer"></div>
|
<div className="pointer" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
top: 0;
|
top: 0;
|
||||||
height: 270px;
|
min-height: 1px;
|
||||||
z-index: $chat-zindex;
|
z-index: $chat-zindex;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
|
@ -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 { FC, useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import { RoomChatFormatter, RoomWidgetChatSelectAvatarMessage, RoomWidgetRoomObjectMessage, RoomWidgetUpdateChatEvent } from '../../../../api';
|
import { GetConfiguration, RoomChatFormatter, RoomWidgetChatSelectAvatarMessage, RoomWidgetRoomObjectMessage, RoomWidgetUpdateChatEvent } from '../../../../api';
|
||||||
import { UseEventDispatcherHook, UseRoomEngineEvent } from '../../../../hooks';
|
import { UseEventDispatcherHook, UseMessageEventHook, UseRoomEngineEvent } from '../../../../hooks';
|
||||||
import { useRoomContext } from '../../RoomContext';
|
import { useRoomContext } from '../../RoomContext';
|
||||||
import { ChatWidgetMessageView } from './ChatWidgetMessageView';
|
import { ChatWidgetMessageView } from './ChatWidgetMessageView';
|
||||||
import { ChatBubbleMessage } from './common/ChatBubbleMessage';
|
import { ChatBubbleMessage } from './common/ChatBubbleMessage';
|
||||||
|
import { DoChatsOverlap } from './common/DoChatsOverlap';
|
||||||
|
|
||||||
export const ChatWidgetView: FC<{}> = props =>
|
export const ChatWidgetView: FC<{}> = props =>
|
||||||
{
|
{
|
||||||
|
const [chatSettings, setChatSettings] = useState<RoomChatSettings>(null);
|
||||||
const [ chatMessages, setChatMessages ] = useState<ChatBubbleMessage[]>([]);
|
const [ chatMessages, setChatMessages ] = useState<ChatBubbleMessage[]>([]);
|
||||||
const { roomSession = null, eventDispatcher = null, widgetHandler = null } = useRoomContext();
|
const { roomSession = null, eventDispatcher = null, widgetHandler = null } = useRoomContext();
|
||||||
const elementRef = useRef<HTMLDivElement>();
|
const elementRef = useRef<HTMLDivElement>();
|
||||||
@ -21,40 +23,66 @@ export const ChatWidgetView: FC<{}> = props =>
|
|||||||
if(newMessages.length !== chatMessages.length) setChatMessages(newMessages);
|
if(newMessages.length !== chatMessages.length) setChatMessages(newMessages);
|
||||||
}, [ chatMessages ]);
|
}, [ chatMessages ]);
|
||||||
|
|
||||||
const moveChatUp = useCallback((chat: ChatBubbleMessage, amount: number) =>
|
|
||||||
{
|
|
||||||
chat.top -= amount;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const moveAllChatsUp = useCallback((amount: number) =>
|
const moveAllChatsUp = useCallback((amount: number) =>
|
||||||
{
|
{
|
||||||
chatMessages.forEach(chat => moveChatUp(chat, amount));
|
chatMessages.forEach(chat => (chat.top -= amount));
|
||||||
|
|
||||||
removeHiddenChats();
|
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 makeRoom = useCallback((chat: ChatBubbleMessage) =>
|
||||||
{
|
{
|
||||||
const lowestPoint = ((chat.top + chat.height) - 1);
|
if(chatSettings.mode === RoomChatSettings.CHAT_MODE_FREE_FLOW)
|
||||||
const requiredSpace = (chat.height + 1);
|
|
||||||
const spaceAvailable = (elementRef.current.offsetHeight - lowestPoint);
|
|
||||||
|
|
||||||
if(spaceAvailable < requiredSpace)
|
|
||||||
{
|
{
|
||||||
const amount = (requiredSpace - spaceAvailable);
|
checkOverlappingChats(chat, 0, [ chat ]);
|
||||||
|
|
||||||
chatMessages.forEach(existingChat =>
|
|
||||||
{
|
|
||||||
if(existingChat === chat) return;
|
|
||||||
|
|
||||||
moveChatUp(existingChat, amount);
|
|
||||||
});
|
|
||||||
|
|
||||||
removeHiddenChats();
|
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) =>
|
const onRoomWidgetUpdateChatEvent = useCallback((event: RoomWidgetUpdateChatEvent) =>
|
||||||
{
|
{
|
||||||
@ -71,23 +99,16 @@ export const ChatWidgetView: FC<{}> = props =>
|
|||||||
event.userImage,
|
event.userImage,
|
||||||
(event.userColor && (('#' + (event.userColor.toString(16).padStart(6, '0'))) || null)));
|
(event.userColor && (('#' + (event.userColor.toString(16).padStart(6, '0'))) || null)));
|
||||||
|
|
||||||
addChat(chatMessage);
|
setChatMessages(prevValue => [ ...prevValue, chatMessage ]);
|
||||||
}, [ addChat ]);
|
}, []);
|
||||||
|
|
||||||
UseEventDispatcherHook(RoomWidgetUpdateChatEvent.CHAT_EVENT, eventDispatcher, onRoomWidgetUpdateChatEvent);
|
UseEventDispatcherHook(RoomWidgetUpdateChatEvent.CHAT_EVENT, eventDispatcher, onRoomWidgetUpdateChatEvent);
|
||||||
|
|
||||||
const onRoomDragEvent = useCallback((event: RoomDragEvent) =>
|
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 => (chat.elementRef && (chat.left += event.offsetX)));
|
||||||
|
|
||||||
chatMessages.forEach(chat =>
|
|
||||||
{
|
|
||||||
if(!chat.elementRef) return;
|
|
||||||
|
|
||||||
chat.left += event.offsetX;
|
|
||||||
});
|
|
||||||
}, [ roomSession, chatMessages ]);
|
}, [ roomSession, chatMessages ]);
|
||||||
|
|
||||||
UseRoomEngineEvent(RoomDragEvent.ROOM_DRAG, onRoomDragEvent);
|
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.processWidgetMessage(new RoomWidgetChatSelectAvatarMessage(RoomWidgetChatSelectAvatarMessage.MESSAGE_SELECT_AVATAR, chat.senderId, chat.username, chat.roomId));
|
||||||
}, [ widgetHandler ]);
|
}, [ 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(() =>
|
useEffect(() =>
|
||||||
{
|
{
|
||||||
const interval = setInterval(() => moveAllChatsUp(15), 4500);
|
const interval = setInterval(() => moveAllChatsUp(15), getScrollSpeed());
|
||||||
|
|
||||||
return () =>
|
return () =>
|
||||||
{
|
{
|
||||||
if(interval) clearInterval(interval);
|
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 (
|
return (
|
||||||
<div ref={ elementRef } className="nitro-chat-widget">
|
<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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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)));
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user