diff --git a/src/views/room/RoomView.tsx b/src/views/room/RoomView.tsx index 0a399fdd..f2534398 100644 --- a/src/views/room/RoomView.tsx +++ b/src/views/room/RoomView.tsx @@ -7,7 +7,7 @@ import { DispatchTouchEvent } from '../../api/nitro/room/DispatchTouchEvent'; import { GetRoomEngine } from '../../api/nitro/room/GetRoomEngine'; import { RoomViewProps } from './RoomView.types'; 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'; export function RoomView(props: RoomViewProps): JSX.Element @@ -92,7 +92,7 @@ export function RoomView(props: RoomViewProps): JSX.Element <> - + } ); diff --git a/src/views/room/widgets/Widgets.scss b/src/views/room/widgets/Widgets.scss index b5258174..71970ddb 100644 --- a/src/views/room/widgets/Widgets.scss +++ b/src/views/room/widgets/Widgets.scss @@ -1,3 +1,3 @@ -@import './chat/ChatWidgetsView'; +@import './chat/ChatWidgetView'; @import './chat-input/ChatInputView'; @import './furniture/FurnitureWidgets'; diff --git a/src/views/room/widgets/chat-input/ChatInputView.tsx b/src/views/room/widgets/chat-input/ChatInputView.tsx index d4e04385..b26fcf3c 100644 --- a/src/views/room/widgets/chat-input/ChatInputView.tsx +++ b/src/views/room/widgets/chat-input/ChatInputView.tsx @@ -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 { GetRoomSession } from '../../../../api'; import { SendChatTypingMessage } from '../../../../api/nitro/session/SendChatTypingMessage'; +import { GetConfiguration } from '../../../../utils/GetConfiguration'; import { LocalizeText } from '../../../../utils/LocalizeText'; -import { ChatInputViewProps } from './ChatInputView.types'; - -// const chatModeIdShout = LocalizeText('widgets.chatinput.mode.shout'); -// const chatModeIdSpeak = LocalizeText('widgets.chatinput.mode.speak'); -// const maxChatLength = GetConfiguration('chat.input.maxlength', 100); +import { ChatInputMessageType, ChatInputViewProps } from './ChatInputView.types'; let lastContent = ''; @@ -15,9 +13,27 @@ export const ChatInputView: FC = props => const [ chatValue, setChatValue ] = useState(''); const [ selectedUsername, setSelectedUsername ] = useState(''); const [ isTyping, setIsTyping ] = useState(false); - const [ maxCharacters, setMaxCharacters ] = useState(100); + const inputRef = useRef(); - const inputRef = createRef(); + 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('chat.input.maxlength', 100); + }, []); const anotherInputHasFocus = useCallback(() => { @@ -49,16 +65,78 @@ export const ChatInputView: FC = props => }); }, [ selectedUsername ]); - const sendChatValue = useCallback((shiftKey: boolean = false) => + const sendChat = useCallback((text: string, chatType: number, recipientName: string = '', styleId: number = 0) => { - if(!chatValue || (chatValue === '')) return; - }, [ chatValue ]); + setChatValue(''); + + 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) => { - if(!inputRef.current) return; - - if(anotherInputHasFocus()) return; + if(!inputRef.current || anotherInputHasFocus()) return; if(document.activeElement !== inputRef.current) setInputFocus(); @@ -68,7 +146,7 @@ export const ChatInputView: FC = props => checkSpecialKeywordForInput(); return; case 'Enter': - sendChatValue(event.shiftKey); + sendChatValue((event.target as HTMLInputElement).value, event.shiftKey); return; case 'Backspace': return; @@ -141,8 +219,7 @@ export const ChatInputView: FC = props =>
- { event.target.parentElement.dataset.value = event.target.value; setChatValue(event.target.value) } } onMouseDown={ onInputMouseDownEvent } /> - {/* */} + { event.target.parentElement.dataset.value = event.target.value; setChatValue(event.target.value) } } onMouseDown={ onInputMouseDownEvent } />
, document.getElementById('toolbar-chat-input-container')) diff --git a/src/views/room/widgets/chat-input/ChatInputView.types.ts b/src/views/room/widgets/chat-input/ChatInputView.types.ts index 829bcdb0..4c21deb6 100644 --- a/src/views/room/widgets/chat-input/ChatInputView.types.ts +++ b/src/views/room/widgets/chat-input/ChatInputView.types.ts @@ -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; +} diff --git a/src/views/room/widgets/chat/ChatWidgetView.scss b/src/views/room/widgets/chat/ChatWidgetView.scss new file mode 100644 index 00000000..67bb611f --- /dev/null +++ b/src/views/room/widgets/chat/ChatWidgetView.scss @@ -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'; +} diff --git a/src/views/room/widgets/chat/ChatWidgetView.tsx b/src/views/room/widgets/chat/ChatWidgetView.tsx new file mode 100644 index 00000000..d2a2ad41 --- /dev/null +++ b/src/views/room/widgets/chat/ChatWidgetView.tsx @@ -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([]); + const elementRef = useRef(); + + 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(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 ( +
+ { chatMessages && (chatMessages.length > 0) && chatMessages.map((chat, index) => + { + return + })} +
+ ); +} diff --git a/src/views/room/widgets/chat/ChatWidgetView.types.ts b/src/views/room/widgets/chat/ChatWidgetView.types.ts new file mode 100644 index 00000000..a4efc070 --- /dev/null +++ b/src/views/room/widgets/chat/ChatWidgetView.types.ts @@ -0,0 +1,4 @@ +export interface ChatWidgetViewProps +{ + +} diff --git a/src/views/room/widgets/chat/ChatWidgetsView.scss b/src/views/room/widgets/chat/ChatWidgetsView.scss deleted file mode 100644 index 4e8db237..00000000 --- a/src/views/room/widgets/chat/ChatWidgetsView.scss +++ /dev/null @@ -1,3 +0,0 @@ -.nitro-chat-widget { - pointer-events: none; -} diff --git a/src/views/room/widgets/chat/ChatWidgetsView.tsx b/src/views/room/widgets/chat/ChatWidgetsView.tsx deleted file mode 100644 index b62f821d..00000000 --- a/src/views/room/widgets/chat/ChatWidgetsView.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { ChatWidgetsViewProps } from './ChatWidgetsView.types'; -import { ChatMessagesWidgetView } from './messages/ChatMessagesWidgetView'; - -export function ChatWidgetsView(props: ChatWidgetsViewProps): JSX.Element -{ - const {} = props; - - return ( -
- -
- ); -} diff --git a/src/views/room/widgets/chat/ChatWidgetsView.types.ts b/src/views/room/widgets/chat/ChatWidgetsView.types.ts deleted file mode 100644 index afb43ce7..00000000 --- a/src/views/room/widgets/chat/ChatWidgetsView.types.ts +++ /dev/null @@ -1,3 +0,0 @@ - -export interface ChatWidgetsViewProps -{} diff --git a/src/views/room/widgets/chat/message/ChatWidgetMessageView.scss b/src/views/room/widgets/chat/message/ChatWidgetMessageView.scss new file mode 100644 index 00000000..d472cfe1 --- /dev/null +++ b/src/views/room/widgets/chat/message/ChatWidgetMessageView.scss @@ -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'); + } + } +} diff --git a/src/views/room/widgets/chat/message/ChatWidgetMessageView.tsx b/src/views/room/widgets/chat/message/ChatWidgetMessageView.tsx new file mode 100644 index 00000000..8003f3b8 --- /dev/null +++ b/src/views/room/widgets/chat/message/ChatWidgetMessageView.tsx @@ -0,0 +1,68 @@ +import { FC, MouseEvent, useCallback, useEffect, useRef, useState } from 'react'; +import { ChatWidgetMessageViewProps } from './ChatWidgetMessageView.types'; + +export const ChatWidgetMessageView: FC = props => +{ + const { chat = null, makeRoom = null } = props; + const [ isVisible, setIsVisible ] = useState(false); + const elementRef = useRef(); + + 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 ( +
+ { (chat.styleId === 0) &&
} +
+
+ { (chat.imageUrl && (chat.imageUrl !== '')) &&
} +
+
+ { chat.username } { chat.text } +
+
+
+
+ ); +} diff --git a/src/views/room/widgets/chat/message/ChatWidgetMessageView.types.ts b/src/views/room/widgets/chat/message/ChatWidgetMessageView.types.ts new file mode 100644 index 00000000..4131ef64 --- /dev/null +++ b/src/views/room/widgets/chat/message/ChatWidgetMessageView.types.ts @@ -0,0 +1,7 @@ +import { ChatBubbleMessage } from '../utils/ChatBubbleMessage'; + +export interface ChatWidgetMessageViewProps +{ + chat: ChatBubbleMessage; + makeRoom: (amount?: number, skipLast?: boolean) => void; +} diff --git a/src/views/room/widgets/chat/utils/ChatBubbleMessage.ts b/src/views/room/widgets/chat/utils/ChatBubbleMessage.ts new file mode 100644 index 00000000..f0cdd112 --- /dev/null +++ b/src/views/room/widgets/chat/utils/ChatBubbleMessage.ts @@ -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; + } +} diff --git a/src/views/room/widgets/chat/utils/ChatWidgetUtilities.ts b/src/views/room/widgets/chat/utils/ChatWidgetUtilities.ts new file mode 100644 index 00000000..61471c53 --- /dev/null +++ b/src/views/room/widgets/chat/utils/ChatWidgetUtilities.ts @@ -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); +}