Chat works

This commit is contained in:
Bill 2021-05-15 16:28:59 -04:00
parent 9f04da5157
commit 091c7d9f90
15 changed files with 1337 additions and 39 deletions

View File

@ -7,7 +7,7 @@ import { DispatchTouchEvent } from '../../api/nitro/room/DispatchTouchEvent';
import { GetRoomEngine } from '../../api/nitro/room/GetRoomEngine'; import { GetRoomEngine } from '../../api/nitro/room/GetRoomEngine';
import { RoomViewProps } from './RoomView.types'; import { RoomViewProps } from './RoomView.types';
import { ChatInputView } from './widgets/chat-input/ChatInputView'; import { ChatInputView } from './widgets/chat-input/ChatInputView';
import { ChatWidgetsView } from './widgets/chat/ChatWidgetsView'; import { ChatWidgetView } from './widgets/chat/ChatWidgetView';
import { FurnitureWidgetsView } from './widgets/furniture/FurnitureWidgetsView'; import { FurnitureWidgetsView } from './widgets/furniture/FurnitureWidgetsView';
export function RoomView(props: RoomViewProps): JSX.Element export function RoomView(props: RoomViewProps): JSX.Element
@ -92,7 +92,7 @@ export function RoomView(props: RoomViewProps): JSX.Element
<> <>
<ChatInputView /> <ChatInputView />
<FurnitureWidgetsView events={ events } /> <FurnitureWidgetsView events={ events } />
<ChatWidgetsView /> <ChatWidgetView />
</> } </> }
</div> </div>
); );

View File

@ -1,3 +1,3 @@
@import './chat/ChatWidgetsView'; @import './chat/ChatWidgetView';
@import './chat-input/ChatInputView'; @import './chat-input/ChatInputView';
@import './furniture/FurnitureWidgets'; @import './furniture/FurnitureWidgets';

View File

@ -1,12 +1,10 @@
import { createRef, FC, MouseEvent, useCallback, useEffect, useState } from 'react'; import { FC, MouseEvent, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { createPortal } from 'react-dom'; import { createPortal } from 'react-dom';
import { GetRoomSession } from '../../../../api';
import { SendChatTypingMessage } from '../../../../api/nitro/session/SendChatTypingMessage'; import { SendChatTypingMessage } from '../../../../api/nitro/session/SendChatTypingMessage';
import { GetConfiguration } from '../../../../utils/GetConfiguration';
import { LocalizeText } from '../../../../utils/LocalizeText'; import { LocalizeText } from '../../../../utils/LocalizeText';
import { ChatInputViewProps } from './ChatInputView.types'; import { ChatInputMessageType, ChatInputViewProps } from './ChatInputView.types';
// const chatModeIdShout = LocalizeText('widgets.chatinput.mode.shout');
// const chatModeIdSpeak = LocalizeText('widgets.chatinput.mode.speak');
// const maxChatLength = GetConfiguration<number>('chat.input.maxlength', 100);
let lastContent = ''; let lastContent = '';
@ -15,9 +13,27 @@ export const ChatInputView: FC<ChatInputViewProps> = props =>
const [ chatValue, setChatValue ] = useState<string>(''); const [ chatValue, setChatValue ] = useState<string>('');
const [ selectedUsername, setSelectedUsername ] = useState(''); const [ selectedUsername, setSelectedUsername ] = useState('');
const [ isTyping, setIsTyping ] = useState(false); const [ isTyping, setIsTyping ] = useState(false);
const [ maxCharacters, setMaxCharacters ] = useState<number>(100); const inputRef = useRef<HTMLInputElement>();
const inputRef = createRef<HTMLInputElement>(); const chatModeIdWhisper = useMemo(() =>
{
return LocalizeText('widgets.chatinput.mode.whisper');
}, []);
const chatModeIdShout = useMemo(() =>
{
return LocalizeText('widgets.chatinput.mode.shout');
}, []);
const chatModeIdSpeak = useMemo(() =>
{
return LocalizeText('widgets.chatinput.mode.speak');
}, []);
const maxChatLength = useMemo(() =>
{
return GetConfiguration<number>('chat.input.maxlength', 100);
}, []);
const anotherInputHasFocus = useCallback(() => const anotherInputHasFocus = useCallback(() =>
{ {
@ -49,16 +65,78 @@ export const ChatInputView: FC<ChatInputViewProps> = props =>
}); });
}, [ selectedUsername ]); }, [ selectedUsername ]);
const sendChatValue = useCallback((shiftKey: boolean = false) => const sendChat = useCallback((text: string, chatType: number, recipientName: string = '', styleId: number = 0) =>
{ {
if(!chatValue || (chatValue === '')) return; setChatValue('');
}, [ chatValue ]);
switch(chatType)
{
case ChatInputMessageType.CHAT_DEFAULT:
GetRoomSession().sendChatMessage(text, styleId);
return;
case ChatInputMessageType.CHAT_WHISPER:
GetRoomSession().sendWhisperMessage(recipientName, text, styleId);
return;
case ChatInputMessageType.CHAT_SHOUT:
GetRoomSession().sendShoutMessage(text, styleId);
return;
}
}, []);
const sendChatValue = useCallback((value: string, shiftKey: boolean = false) =>
{
if(!value || (value === '')) return;
let chatType = (shiftKey ? ChatInputMessageType.CHAT_SHOUT : ChatInputMessageType.CHAT_DEFAULT);
let text = value;
const parts = text.split(' ');
let recipientName = '';
let append = '';
switch(parts[0])
{
case chatModeIdWhisper:
chatType = ChatInputMessageType.CHAT_WHISPER;
recipientName = parts[1];
append = (chatModeIdWhisper + ' ' + recipientName + ' ');
parts.shift();
parts.shift();
break;
case chatModeIdShout:
chatType = ChatInputMessageType.CHAT_SHOUT;
parts.shift();
break;
case chatModeIdSpeak:
chatType = ChatInputMessageType.CHAT_DEFAULT;
parts.shift();
break;
}
text = parts.join(' ');
if(text.length <= maxChatLength)
{
// if(this.needsStyleUpdate)
// {
// Nitro.instance.sessionDataManager.sendChatStyleUpdate(this.currentStyle);
// this.needsStyleUpdate = false;
// }
let currentStyle = 1;
sendChat(text, chatType, recipientName, currentStyle);
}
}, [ chatModeIdWhisper, chatModeIdShout, chatModeIdSpeak, maxChatLength, sendChat ]);
const onKeyDownEvent = useCallback((event: KeyboardEvent) => const onKeyDownEvent = useCallback((event: KeyboardEvent) =>
{ {
if(!inputRef.current) return; if(!inputRef.current || anotherInputHasFocus()) return;
if(anotherInputHasFocus()) return;
if(document.activeElement !== inputRef.current) setInputFocus(); if(document.activeElement !== inputRef.current) setInputFocus();
@ -68,7 +146,7 @@ export const ChatInputView: FC<ChatInputViewProps> = props =>
checkSpecialKeywordForInput(); checkSpecialKeywordForInput();
return; return;
case 'Enter': case 'Enter':
sendChatValue(event.shiftKey); sendChatValue((event.target as HTMLInputElement).value, event.shiftKey);
return; return;
case 'Backspace': case 'Backspace':
return; return;
@ -141,8 +219,7 @@ export const ChatInputView: FC<ChatInputViewProps> = props =>
<div className="nitro-chat-input"> <div className="nitro-chat-input">
<div className="chatinput-container"> <div className="chatinput-container">
<div className="input-sizer"> <div className="input-sizer">
<input ref={ inputRef } type="text" className="chat-input" placeholder={ LocalizeText('widgets.chatinput.default') } value={ chatValue } maxLength={ maxCharacters } onChange={ event => { event.target.parentElement.dataset.value = event.target.value; setChatValue(event.target.value) } } onMouseDown={ onInputMouseDownEvent } /> <input ref={ inputRef } type="text" className="chat-input" placeholder={ LocalizeText('widgets.chatinput.default') } value={ chatValue } maxLength={ maxChatLength } onChange={ event => { event.target.parentElement.dataset.value = event.target.value; setChatValue(event.target.value) } } onMouseDown={ onInputMouseDownEvent } />
{/* <input #chatInputView type="text" class="chat-input" placeholder="{{ 'widgets.chatinput.default' | translate }}" (input)="chatInputView.parentElement.dataset.value = chatInputView.value" [disabled]="floodBlocked" [maxLength]="inputMaxLength" /> */}
</div> </div>
</div> </div>
</div>, document.getElementById('toolbar-chat-input-container')) </div>, document.getElementById('toolbar-chat-input-container'))

View File

@ -2,3 +2,10 @@ export interface ChatInputViewProps
{ {
} }
export class ChatInputMessageType
{
public static CHAT_DEFAULT: number = 0;
public static CHAT_WHISPER: number = 1;
public static CHAT_SHOUT: number = 2;
}

View File

@ -0,0 +1,16 @@
.nitro-chat-widget {
position: absolute;
display: flex;
justify-content: center;
align-items: center;
width: 100%;
top: 0;
height: 270px;
z-index: $chat-zindex;
background-color: transparent;
border-radius: 0;
box-shadow: none;
pointer-events: none;
@import './message/ChatWidgetMessageView';
}

View File

@ -0,0 +1,177 @@
import { RoomObjectCategory, RoomSessionChatEvent } from 'nitro-renderer';
import { useCallback, useEffect, useRef, useState } from 'react';
import { GetRoomEngine, GetRoomSession } from '../../../../api';
import { useRoomSessionManagerEvent } from '../../../../hooks/events/nitro/session/room-session-manager-event';
import { ChatWidgetViewProps } from './ChatWidgetView.types';
import { ChatWidgetMessageView } from './message/ChatWidgetMessageView';
import { ChatBubbleMessage } from './utils/ChatBubbleMessage';
import { GetBubbleLocation } from './utils/ChatWidgetUtilities';
export function ChatWidgetView(props: ChatWidgetViewProps): JSX.Element
{
const {} = props;
const [ chatMessages, setChatMessages ] = useState<ChatBubbleMessage[]>([]);
const elementRef = useRef<HTMLDivElement>();
const removeLastHiddenChat = useCallback(() =>
{
if(!chatMessages.length) return;
const lastChat = chatMessages[chatMessages.length - 1];
if((lastChat.lastTop > -(lastChat.height))) return;
setChatMessages(prevValue =>
{
const newMessages = [ ...prevValue ];
newMessages.splice((newMessages.length - 1), 1);
return newMessages;
});
}, [ chatMessages ]);
const moveChatUp = useCallback((chat: ChatBubbleMessage, amount: number) =>
{
if(!chat.elementRef) return;
let y = chat.elementRef.offsetHeight;
if(amount > 0) y = amount;
let top = (chat.elementRef.offsetTop - y);
chat.lastTop = top;
chat.elementRef.style.top = (top + 'px');
}, []);
const moveAllChatsUp = useCallback((amount: number) =>
{
chatMessages.forEach(chat => moveChatUp(chat, amount));
}, [ chatMessages, moveChatUp ]);
const makeRoom = useCallback((amount: number = 0, skipLast: boolean = false) =>
{
const lastChat = chatMessages[chatMessages.length - 1];
if(!lastChat) return;
const lowestPoint = ((lastChat.lastTop + lastChat.height) - 1);
const requiredSpace = ((amount || lastChat.height) + 1);
const spaceAvailable = (elementRef.current.offsetHeight - lowestPoint);
if(spaceAvailable < requiredSpace)
{
amount = (requiredSpace - spaceAvailable);
chatMessages.forEach((chat, index) =>
{
if(skipLast && (index === (chatMessages.length - 1))) return;
moveChatUp(chat, amount)
});
}
}, [ chatMessages, moveChatUp ]);
const addChat = useCallback((chat: ChatBubbleMessage) =>
{
setChatMessages(prevValue =>
{
return [ ...prevValue, chat ]
});
}, []);
const onRoomSessionChatEvent = useCallback((event: RoomSessionChatEvent) =>
{
const roomObject = GetRoomEngine().getRoomObject(event.session.roomId, event.objectId, RoomObjectCategory.UNIT);
if(!roomObject) return;
const canvasId = 1;
const roomGeometry = GetRoomEngine().getRoomInstanceGeometry(event.session.roomId, canvasId);
if(!roomGeometry) return;
const objectLocation = roomObject.getLocation();
const bubbleLocation = GetBubbleLocation(event.session.roomId, objectLocation, canvasId);
const userData = GetRoomSession().userDataManager.getUserDataByIndex(event.objectId);
let username = '';
let avatarColor = '';
let imageUrl: string = null;
let chatType = event.chatType;
let styleId = event.style;
let userType = 0;
let petType = -1;
let text = event.message;
if(userData)
{
userType = userData.type;
const figure = userData.figure;
// switch(userType)
// {
// case RoomObjectType.PET:
// image = this.getPetImage(figure, 2, true, 64, roomObject.model.getValue<string>(RoomObjectVariable.FIGURE_POSTURE));
// petType = new PetFigureData(figure).typeId;
// break;
// case RoomObjectType.USER:
// image = this.getUserImage(figure);
// break;
// case RoomObjectType.RENTABLE_BOT:
// case RoomObjectType.BOT:
// styleId = SystemChatStyleEnum.BOT;
// break;
// }
//avatarColor = this._avatarColorCache.get(figure);
username = userData.name;
}
const chatMessage = new ChatBubbleMessage(
text,
username,
bubbleLocation,
chatType,
styleId,
imageUrl,
avatarColor
);
addChat(chatMessage);
}, [ addChat ]);
useRoomSessionManagerEvent(RoomSessionChatEvent.CHAT_EVENT, onRoomSessionChatEvent);
useEffect(() =>
{
const interval = setInterval(() => moveAllChatsUp(15), 500);
return () =>
{
if(interval) clearInterval(interval);
}
}, [ chatMessages, moveAllChatsUp ]);
useEffect(() =>
{
const interval = setInterval(() => removeLastHiddenChat(), 500);
return () =>
{
if(interval) clearInterval(interval);
}
}, [ removeLastHiddenChat ]);
return (
<div ref={ elementRef } className="nitro-chat-widget">
{ chatMessages && (chatMessages.length > 0) && chatMessages.map((chat, index) =>
{
return <ChatWidgetMessageView key={ index } chat={ chat } makeRoom={ makeRoom } />
})}
</div>
);
}

View File

@ -0,0 +1,4 @@
export interface ChatWidgetViewProps
{
}

View File

@ -1,3 +0,0 @@
.nitro-chat-widget {
pointer-events: none;
}

View File

@ -1,13 +0,0 @@
import { ChatWidgetsViewProps } from './ChatWidgetsView.types';
import { ChatMessagesWidgetView } from './messages/ChatMessagesWidgetView';
export function ChatWidgetsView(props: ChatWidgetsViewProps): JSX.Element
{
const {} = props;
return (
<div className="nitro-chat-widget">
<ChatMessagesWidgetView />
</div>
);
}

View File

@ -1,3 +0,0 @@
export interface ChatWidgetsViewProps
{}

View File

@ -0,0 +1,904 @@
@keyframes bounceIn {
0% {
opacity: 0;
transform: scale(.3);
}
50% {
opacity: 1;
transform: scale(1.1);
}
70% {
transform: scale(.9);
}
100% {
transform: scale(1);
}
}
.bubble-container {
position: absolute;
width: fit-content;
transition: top 0.2s ease 0s;
user-select: none;
pointer-events: all;
// -webkit-animation-duration: 0.2s;
// animation-duration: 0.2s;
// -webkit-animation-fill-mode: both;
// animation-fill-mode: both;
// -webkit-animation-name: bounceIn;
// animation-name: bounceIn;
.user-container-bg {
position: absolute;
top: -1px;
left: 1px;
width: 30px;
height: calc(100% - 0.5px);
border-radius: 7px;
z-index: 1;
}
.chat-bubble {
position: relative;
z-index: 1;
word-break: break-word;
max-width: 350px;
min-height: 25px;
font-size: $font-size-sm;
border-image-slice: 17 6 6 29 fill;
border-image-width: 17px 6px 6px 29px;
border-image-outset: 2px 0px 0px 0px;
border-image-repeat: repeat repeat;
.pointer {
position: absolute;
left: 50%;
transform: translateX(-50%);
width: 9px;
height: 6px;
bottom: -5px;
}
&.type-0 { // normal
.message {
font-weight: 400;
}
}
&.type-1 { // whisper
.message {
font-weight: 400;
font-style: italic;
color: #595959;
}
}
&.type-2 { // shout
.message {
font-weight: 700;
}
}
&.bubble-0 {
border-image-source: url('../../../../../assets/images/chat/chatbubbles/bubble_0_transparent.png');
.pointer {
background: url('../../../../../assets/images/chat/chatbubbles/bubble_0_1_33_34_pointer.png');
bottom: -5px;
}
}
&.bubble-1 {
border-image-source: url('../../../../../assets/images/chat/chatbubbles/bubble_1.png');
border-image-slice: 18 6 6 29 fill;
border-image-width: 18px 6px 6px 29px;
border-image-outset: 3px 0px 0px 0px;
.user-container {
display: none;
}
.username {
display: none;
}
.pointer {
background: url('../../../../../assets/images/chat/chatbubbles/bubble_0_1_33_34_pointer.png');
}
}
&.bubble-2, &.bubble-31 {
border-image-source: url('../../../../../assets/images/chat/chatbubbles/bubble_2.png');
.user-container {
display: none;
}
.username {
color: rgba($white, 1);
}
.message {
color: rgba($white, 1) !important;
}
.pointer {
background: url('../../../../../assets/images/chat/chatbubbles/bubble_2_31_pointer.png');
height: 7px;
}
}
&.bubble-3 {
border-image-source: url('../../../../../assets/images/chat/chatbubbles/bubble_3.png');
.pointer {
background: url('../../../../../assets/images/chat/chatbubbles/bubble_3_pointer.png');
}
}
&.bubble-4 {
border-image-source: url('../../../../../assets/images/chat/chatbubbles/bubble_4.png');
.pointer {
background: url('../../../../../assets/images/chat/chatbubbles/bubble_4_pointer.png');
}
}
&.bubble-5 {
border-image-source: url('../../../../../assets/images/chat/chatbubbles/bubble_5.png');
.pointer {
background: url('../../../../../assets/images/chat/chatbubbles/bubble_5_pointer.png');
}
}
&.bubble-6 {
border-image-source: url('../../../../../assets/images/chat/chatbubbles/bubble_6.png');
.pointer {
background: url('../../../../../assets/images/chat/chatbubbles/bubble_6_pointer.png');
}
}
&.bubble-7 {
border-image-source: url('../../../../../assets/images/chat/chatbubbles/bubble_7.png');
.pointer {
background: url('../../../../../assets/images/chat/chatbubbles/bubble_7_pointer.png');
}
}
&.bubble-8 {
border-image-source: url('../../../../../assets/images/chat/chatbubbles/bubble_8.png');
border-image-slice: 20 6 6 27 fill;
border-image-width: 20px 6px 6px 27px;
border-image-outset: 5px 0px 0px 0px;
.chat-content {
color: #fff;
}
.pointer {
background: url('../../../../../assets/images/chat/chatbubbles/bubble_8_pointer.png');
}
}
&.bubble-9 {
border-image-source: url('../../../../../assets/images/chat/chatbubbles/bubble_9.png');
border-image-slice: 17 18 12 19 fill;
border-image-width: 17px 18px 12px 19px;
border-image-outset: 7px 7px 0px 9px;
.chat-content {
margin-left: 20px;
}
.pointer {
background: url('../../../../../assets/images/chat/chatbubbles/bubble_9_pointer.png');
width: 7px;
height: 10px;
bottom: -6px;
}
}
&.bubble-10 {
border-image-source: url('../../../../../assets/images/chat/chatbubbles/bubble_10.png');
border-image-slice: 29 18 8 37 fill;
border-image-width: 29px 18px 8px 37px;
border-image-outset: 12px 7px 1px 5px;
.chat-content {
margin-left: 24px;
color: #fff;
}
.pointer {
background: url('../../../../../assets/images/chat/chatbubbles/bubble_10_pointer.png');
width: 7px;
height: 8px;
bottom: -3px;
}
}
&.bubble-11 {
border-image-source: url('../../../../../assets/images/chat/chatbubbles/bubble_11.png');
.pointer {
background: url('../../../../../assets/images/chat/chatbubbles/bubble_11_pointer.png');
}
}
&.bubble-12 {
border-image-source: url('../../../../../assets/images/chat/chatbubbles/bubble_12.png');
.pointer {
background: url('../../../../../assets/images/chat/chatbubbles/bubble_12_pointer.png');
}
}
&.bubble-13 {
border-image-source: url('../../../../../assets/images/chat/chatbubbles/bubble_13.png');
.pointer {
background: url('../../../../../assets/images/chat/chatbubbles/bubble_13_pointer.png');
}
}
&.bubble-14 {
border-image-source: url('../../../../../assets/images/chat/chatbubbles/bubble_14.png');
.chat-content {
color: #fff;
}
.pointer {
background: url('../../../../../assets/images/chat/chatbubbles/bubble_14_pointer.png');
}
}
&.bubble-15 {
border-image-source: url('../../../../../assets/images/chat/chatbubbles/bubble_15.png');
.chat-content {
color: #fff;
}
.pointer {
background: url('../../../../../assets/images/chat/chatbubbles/bubble_15_pointer.png');
}
}
&.bubble-16 {
border-image-source: url('../../../../../assets/images/chat/chatbubbles/bubble_16.png');
border-image-slice: 13 6 10 31 fill;
border-image-width: 13px 6px 10px 31px;
border-image-outset: 6px 0px 0px 0px;
.pointer {
background: url('../../../../../assets/images/chat/chatbubbles/bubble_16_pointer.png');
height: 8px;
}
}
&.bubble-17 {
border-image-source: url('../../../../../assets/images/chat/chatbubbles/bubble_17.png');
border-image-slice: 24 6 8 35 fill;
border-image-width: 24px 6px 8px 35px;
border-image-outset: 9px 0px 2px 5px;
.pointer {
background: url('../../../../../assets/images/chat/chatbubbles/bubble_17_pointer.png');
}
}
&.bubble-18 {
border-image-source: url('../../../../../assets/images/chat/chatbubbles/bubble_18.png');
border-image-slice: 7 16 8 16 fill;
border-image-width: 7px 16px 8px 16px;
border-image-outset: 3px 10px 2px 11px;
.chat-content {
margin-left: 20px;
}
.pointer {
background: url('../../../../../assets/images/chat/chatbubbles/bubble_18_pointer.png');
height: 8px;
}
}
&.bubble-19 {
border-image-source: url('../../../../../assets/images/chat/chatbubbles/bubble_19.png');
border-image-slice: 17 6 9 19 fill;
border-image-width: 17px 6px 9px 19px;
border-image-outset: 5px 0px 0px 8px;
.chat-content {
margin-left: 20px;
}
.pointer {
background: url('../../../../../assets/images/chat/chatbubbles/bubble_19_20_pointer.png');
}
}
&.bubble-20 {
border-image-source: url('../../../../../assets/images/chat/chatbubbles/bubble_20.png');
border-image-slice: 18 6 8 19 fill;
border-image-width: 18px 6px 8px 19px;
border-image-outset: 5px 0px 0px 8px;
.chat-content {
margin-left: 20px;
}
.pointer {
background: url('../../../../../assets/images/chat/chatbubbles/bubble_19_20_pointer.png');
}
}
&.bubble-21 {
border-image-source: url('../../../../../assets/images/chat/chatbubbles/bubble_21.png');
border-image-slice: 20 6 12 24 fill;
border-image-width: 20px 6px 12px 24px;
border-image-outset: 13px 2px 1px 3px;
.chat-content {
margin-left: 20px;
}
.pointer {
background: url('../../../../../assets/images/chat/chatbubbles/bubble_21_pointer.png');
bottom: -4px;
}
}
&.bubble-22 {
border-image-source: url('../../../../../assets/images/chat/chatbubbles/bubble_22.png');
border-image-slice: 18 19 11 33 fill;
border-image-width: 18px 19px 11px 33px;
border-image-outset: 7px 1px 1px 5px;
.chat-content {
margin-left: 20px;
}
.pointer {
background: url('../../../../../assets/images/chat/chatbubbles/bubble_22_pointer.png');
}
}
&.bubble-23 {
border-image-source: url('../../../../../assets/images/chat/chatbubbles/bubble_23.png');
border-image-slice: 16 6 7 32 fill;
border-image-width: 16px 6px 7px 32px;
border-image-outset: 5px 0px 0px 3px;
.pointer {
background: url('../../../../../assets/images/chat/chatbubbles/bubble_23_37_pointer.png');
}
}
&.bubble-24 {
border-image-source: url('../../../../../assets/images/chat/chatbubbles/bubble_24.png');
border-image-slice: 23 8 6 40 fill;
border-image-width: 23px 8px 6px 40px;
border-image-outset: 6px 0px 0px 6px;
.chat-content {
margin-left: 30px;
color: #fff;
}
.pointer {
background: url('../../../../../assets/images/chat/chatbubbles/bubble_24_pointer.png');
bottom: -4px;
}
}
&.bubble-25 {
border-image-source: url('../../../../../assets/images/chat/chatbubbles/bubble_25.png');
border-image-slice: 10 13 8 28 fill;
border-image-width: 10px 13px 8px 28px;
border-image-outset: 6px 3px 2px 0px;
.chat-content {
color: #fff;
}
.pointer {
background: url('../../../../../assets/images/chat/chatbubbles/bubble_25_pointer.png');
height: 9px;
bottom: -7px;
}
}
&.bubble-26 {
border-image-source: url('../../../../../assets/images/chat/chatbubbles/bubble_26.png');
border-image-slice: 16 9 8 29 fill;
border-image-width: 16px 9px 8px 29px;
border-image-outset: 2px 2px 2px 0px;
.chat-content {
color: #c59432;
text-shadow: 1px 1px rgba(0, 0, 0, .3);
}
.pointer {
background: url('../../../../../assets/images/chat/chatbubbles/bubble_26_pointer.png');
height: 10px;
bottom: -6px;
}
}
&.bubble-27 {
border-image-source: url('../../../../../assets/images/chat/chatbubbles/bubble_27.png');
border-image-slice: 25 6 5 36 fill;
border-image-width: 25px 6px 5px 36px;
border-image-outset: 8px 0px 0px 5px;
.chat-content {
margin-left: 30px;
color: #fff;
}
.pointer {
background: url('../../../../../assets/images/chat/chatbubbles/bubble_27_pointer.png');
}
}
&.bubble-28 {
border-image-source: url('../../../../../assets/images/chat/chatbubbles/bubble_28.png');
border-image-slice: 16 7 7 27 fill;
border-image-width: 16px 7px 7px 27px;
border-image-outset: 3px 0px 0px 0px;
.chat-content {
margin-left: 25px;
}
.pointer {
background: url('../../../../../assets/images/chat/chatbubbles/bubble_28_pointer.png');
}
}
&.bubble-29 {
border-image-source: url('../../../../../assets/images/chat/chatbubbles/bubble_29.png');
border-image-slice: 10 7 15 31 fill;
border-image-width: 10px 7px 15px 31px;
border-image-outset: 2px 0px 0px 1px;
.pointer {
background: url('../../../../../assets/images/chat/chatbubbles/bubble_29_pointer.png');
bottom: -4px;
}
}
&.bubble-30 {
border-image-source: url('../../../../../assets/images/chat/chatbubbles/bubble_30.png');
.user-container {
display: none;
}
.pointer {
background: url('../../../../../assets/images/chat/chatbubbles/bubble_30_pointer.png');
height: 7px;
}
}
&.bubble-32 {
border-image-source: url('../../../../../assets/images/chat/chatbubbles/bubble_32.png');
border-image-slice: 15 7 7 30 fill;
border-image-width: 15px 7px 7px 30px;
border-image-outset: 2px 0px 0px 0px;
.pointer {
background: url('../../../../../assets/images/chat/chatbubbles/bubble_32_pointer.png');
}
}
&.bubble-33 {
border-image-source: url('../../../../../assets/images/chat/chatbubbles/bubble_33_34.png');
border-image-slice: 7 6 6 39 fill;
border-image-width: 7px 6px 6px 39px;
border-image-outset: 2px 0px 0px 0px;
.user-container {
display: none;
}
.chat-content {
margin-left: 35px;
}
&::before {
content: ' ';
position: absolute;
width: 19px;
height: 19px;
left: 9px;
top: 2px;
background: url('../../../../../assets/images/chat/chatbubbles/bubble_33_extra.png');
}
.pointer {
background: url('../../../../../assets/images/chat/chatbubbles/bubble_0_1_33_34_pointer.png');
}
}
&.bubble-34 {
border-image-source: url('../../../../../assets/images/chat/chatbubbles/bubble_33_34.png');
border-image-slice: 7 6 6 39 fill;
border-image-width: 7px 6px 6px 39px;
border-image-outset: 2px 0px 0px 0px;
&.type-1 {
.message {
font-style: unset;
color: inherit;
}
}
.user-container {
display: none;
}
.username {
display: none;
}
.chat-content {
margin-left: 35px;
}
&::before {
content: ' ';
position: absolute;
width: 19px;
height: 19px;
left: 9px;
top: 2px;
background: url('../../../../../assets/images/chat/chatbubbles/bubble_34_extra.png');
}
.pointer {
background: url('../../../../../assets/images/chat/chatbubbles/bubble_0_1_33_34_pointer.png');
}
}
&.bubble-35 {
border-image-source: url('../../../../../assets/images/chat/chatbubbles/bubble_35.png');
border-image-slice: 19 6 5 29 fill;
border-image-width: 19px 6px 5px 29px;
border-image-outset: 4px 0px 0px 0px;
.user-container {
display: none;
}
.pointer {
background: url('../../../../../assets/images/chat/chatbubbles/bubble_35_pointer.png');
}
}
&.bubble-36 {
border-image-source: url('../../../../../assets/images/chat/chatbubbles/bubble_36.png');
border-image-slice: 17 7 5 30 fill;
border-image-width: 17px 7px 5px 30px;
border-image-outset: 2px 0px 0px 0px;
.user-container {
display: none;
}
&::before {
content: ' ';
position: absolute;
width: 13px;
height: 18px;
left: 5px;
top: 2px;
background: url('../../../../../assets/images/chat/chatbubbles/bubble_36_extra.png');
}
.pointer {
background: url('../../../../../assets/images/chat/chatbubbles/bubble_36_pointer.png');
}
}
&.bubble-37 {
border-image-source: url('../../../../../assets/images/chat/chatbubbles/bubble_37.png');
border-image-slice: 16 6 7 32 fill;
border-image-width: 16px 6px 7px 32px;
border-image-outset: 5px 0px 0px 3px;
.pointer {
background: url('../../../../../assets/images/chat/chatbubbles/bubble_23_37_pointer.png');
}
}
&.bubble-38 {
border-image-source: url('../../../../../assets/images/chat/chatbubbles/bubble_38.png');
border-image-slice: 17 7 5 30 fill;
border-image-width: 17px 7px 5px 30px;
border-image-outset: 2px 0px 0px 0px;
.user-container {
display: none;
}
&::before {
content: ' ';
position: absolute;
width: 19px;
height: 19px;
left: 3px;
top: 2px;
background: url('../../../../../assets/images/chat/chatbubbles/bubble_38_extra.png');
}
.pointer {
background: url('../../../../../assets/images/chat/chatbubbles/bubble_38_pointer.png');
}
}
.user-container {
z-index: 3;
display: flex;
align-items: center;
justify-content: center;
height: 100%;
max-height: 24px;
image-rendering: -webkit-optimize-contrast;
overflow: hidden;
.user-image {
position: absolute;
top: -48px;
left: -32.5px;
width: 90px;
height: 130px;
background-repeat: no-repeat;
background-position: center;
transform: scale(0.5) translateZ(0);
overflow: hidden;
}
}
.chat-content {
padding: 5px 6px 5px 4px;
margin-left: 27px;
line-height: 1;
color: $black;
min-height: 25px;
}
}
}
.chat-bubble-icon {
background-repeat: no-repeat;
background-position: center;
&.bubble-0 {
background-image: url('../../../../../assets/images/chat/chatbubbles/bubble_0.png');
}
&.bubble-1 {
background-image: url('../../../../../assets/images/chat/chatbubbles/bubble_1.png');
height: 25px;
}
&.bubble-2, &.bubble-31 {
background-image: url('../../../../../assets/images/chat/chatbubbles/bubble_2.png');
}
&.bubble-3 {
background-image: url('../../../../../assets/images/chat/chatbubbles/bubble_3.png');
}
&.bubble-4 {
background-image: url('../../../../../assets/images/chat/chatbubbles/bubble_4.png');
}
&.bubble-5 {
background-image: url('../../../../../assets/images/chat/chatbubbles/bubble_5.png');
}
&.bubble-6 {
background-image: url('../../../../../assets/images/chat/chatbubbles/bubble_6.png');
}
&.bubble-7 {
background-image: url('../../../../../assets/images/chat/chatbubbles/bubble_7.png');
}
&.bubble-8 {
background-image: url('../../../../../assets/images/chat/chatbubbles/bubble_8.png');
}
&.bubble-9 {
background-image: url('../../../../../assets/images/chat/chatbubbles/bubble_9.png');
}
&.bubble-10 {
background-image: url('../../../../../assets/images/chat/chatbubbles/bubble_10.png');
}
&.bubble-11 {
background-image: url('../../../../../assets/images/chat/chatbubbles/bubble_11.png');
}
&.bubble-12 {
background-image: url('../../../../../assets/images/chat/chatbubbles/bubble_12.png');
}
&.bubble-13 {
background-image: url('../../../../../assets/images/chat/chatbubbles/bubble_13.png');
}
&.bubble-14 {
background-image: url('../../../../../assets/images/chat/chatbubbles/bubble_14.png');
}
&.bubble-15 {
background-image: url('../../../../../assets/images/chat/chatbubbles/bubble_15.png');
}
&.bubble-16 {
background-image: url('../../../../../assets/images/chat/chatbubbles/bubble_16.png');
}
&.bubble-17 {
background-image: url('../../../../../assets/images/chat/chatbubbles/bubble_17.png');
}
&.bubble-18 {
background-image: url('../../../../../assets/images/chat/chatbubbles/bubble_18.png');
}
&.bubble-19 {
background-image: url('../../../../../assets/images/chat/chatbubbles/bubble_19.png');
}
&.bubble-20 {
background-image: url('../../../../../assets/images/chat/chatbubbles/bubble_20.png');
}
&.bubble-21 {
background-image: url('../../../../../assets/images/chat/chatbubbles/bubble_21.png');
}
&.bubble-22 {
background-image: url('../../../../../assets/images/chat/chatbubbles/bubble_22.png');
}
&.bubble-23 {
background-image: url('../../../../../assets/images/chat/chatbubbles/bubble_23.png');
}
&.bubble-24 {
background-image: url('../../../../../assets/images/chat/chatbubbles/bubble_24.png');
}
&.bubble-25 {
background-image: url('../../../../../assets/images/chat/chatbubbles/bubble_25.png');
}
&.bubble-26 {
background-image: url('../../../../../assets/images/chat/chatbubbles/bubble_26.png');
}
&.bubble-27 {
background-image: url('../../../../../assets/images/chat/chatbubbles/bubble_27.png');
}
&.bubble-28 {
background-image: url('../../../../../assets/images/chat/chatbubbles/bubble_28.png');
}
&.bubble-29 {
background-image: url('../../../../../assets/images/chat/chatbubbles/bubble_29.png');
}
&.bubble-30 {
background-image: url('../../../../../assets/images/chat/chatbubbles/bubble_30.png');
}
&.bubble-32 {
background-image: url('../../../../../assets/images/chat/chatbubbles/bubble_32.png');
}
&.bubble-33 {
background-image: url('../../../../../assets/images/chat/chatbubbles/bubble_33_34.png');
&::before {
content: ' ';
position: absolute;
width: 19px;
height: 19px;
left: 11px;
top: 10px;
background: url('../../../../../assets/images/chat/chatbubbles/bubble_33_extra.png');
}
}
&.bubble-34 {
background-image: url('../../../../../assets/images/chat/chatbubbles/bubble_33_34.png');
&::before {
content: ' ';
position: absolute;
width: 19px;
height: 19px;
left: 11px;
top: 10px;
background: url('../../../../../assets/images/chat/chatbubbles/bubble_34_extra.png');
}
}
&.bubble-35 {
background-image: url('../../../../../assets/images/chat/chatbubbles/bubble_35.png');
}
&.bubble-36 {
background-image: url('../../../../../assets/images/chat/chatbubbles/bubble_36.png');
&::before {
content: ' ';
position: absolute;
width: 13px;
height: 18px;
left: 13px;
top: 10px;
background: url('../../../../../assets/images/chat/chatbubbles/bubble_36_extra.png');
}
}
&.bubble-37 {
background-image: url('../../../../../assets/images/chat/chatbubbles/bubble_35.png');
}
&.bubble-38 {
background-image: url('../../../../../assets/images/chat/chatbubbles/bubble_38.png');
&::before {
content: ' ';
position: absolute;
width: 19px;
height: 19px;
left: 11px;
top: 10px;
background: url('../../../../../assets/images/chat/chatbubbles/bubble_38_extra.png');
}
}
}

View File

@ -0,0 +1,68 @@
import { FC, MouseEvent, useCallback, useEffect, useRef, useState } from 'react';
import { ChatWidgetMessageViewProps } from './ChatWidgetMessageView.types';
export const ChatWidgetMessageView: FC<ChatWidgetMessageViewProps> = props =>
{
const { chat = null, makeRoom = null } = props;
const [ isVisible, setIsVisible ] = useState(false);
const elementRef = useRef<HTMLDivElement>();
const onClick = useCallback((event: MouseEvent) =>
{
}, []);
useEffect(() =>
{
const element = elementRef.current;
if(!element) return;
const width = element.offsetWidth;
const height = element.offsetHeight;
chat.width = width;
chat.height = height;
chat.elementRef = element;
if(isVisible || !chat) return;
let left = chat.lastLeft;
let top = chat.lastTop;
if(!left && !top)
{
left = (chat.location.x - (width / 2));
top = (element.parentElement.offsetHeight - height);
}
chat.lastLeft = left;
chat.lastTop = top;
element.style.left = (left + 'px');
element.style.top = (top + 'px');
makeRoom(0, true);
setIsVisible(true);
return () =>
{
chat.elementRef = null;
}
}, [ isVisible, elementRef, chat, makeRoom ]);
return (
<div ref={ elementRef } className="bubble-container" style={ { visibility: (isVisible ? 'visible' : 'hidden') } }>
{ (chat.styleId === 0) && <div className="user-container-bg" style={ { backgroundColor: chat.color } } /> }
<div className={ 'chat-bubble bubble-' + chat.styleId + ' type-' + chat.type } onClick={ onClick }>
<div className="user-container">
{ (chat.imageUrl && (chat.imageUrl !== '')) && <div className="user-image" style={ { backgroundImage: 'url(' + chat.imageUrl + ')' } } /> }
</div>
<div className="chat-content">
<b className="username mr-1">{ chat.username }</b><span className="message"> { chat.text }</span>
</div>
<div className="pointer"></div>
</div>
</div>
);
}

View File

@ -0,0 +1,7 @@
import { ChatBubbleMessage } from '../utils/ChatBubbleMessage';
export interface ChatWidgetMessageViewProps
{
chat: ChatBubbleMessage;
makeRoom: (amount?: number, skipLast?: boolean) => void;
}

View File

@ -0,0 +1,25 @@
import { INitroPoint } from 'nitro-renderer';
export class ChatBubbleMessage
{
public static BUBBLE_COUNTER: number = -1;
public id: number = -1;
public width: number = 0;
public height: number = 0;
public lastTop: number = 0;
public lastLeft: number = 0;
public elementRef: HTMLDivElement = null;
constructor(
public text: string = '',
public username: string = '',
public location: INitroPoint = null,
public type: number = 0,
public styleId: number = 0,
public imageUrl: string = null,
public color: string = null
) {
this.id = ++ChatBubbleMessage.BUBBLE_COUNTER;
}
}

View File

@ -0,0 +1,32 @@
import { INitroPoint, IVector3D, NitroPoint } from 'nitro-renderer';
import { GetRoomEngine } from '../../../../../api';
export function GetBubbleLocation(roomId: number, userLocation: IVector3D, canvasId = 1): INitroPoint
{
const geometry = GetRoomEngine().getRoomInstanceGeometry(roomId, canvasId);
const scale = GetRoomEngine().getRoomInstanceRenderingCanvasScale(roomId, canvasId);
let x = ((document.body.offsetWidth * scale) / 2);
let y = ((document.body.offsetHeight * scale) / 2);
if(geometry && userLocation)
{
const screenPoint = geometry.getScreenPoint(userLocation);
if(screenPoint)
{
x = (x + (screenPoint.x * scale));
y = (y + (screenPoint.y * scale));
const offsetPoint = GetRoomEngine().getRoomInstanceRenderingCanvasOffset(roomId, canvasId);
if(offsetPoint)
{
x = (x + offsetPoint.x);
y = (y + offsetPoint.y);
}
}
}
return new NitroPoint(x, y);
}