mirror of
https://github.com/duckietm/Nitro-Cool-UI.git
synced 2025-06-21 22:36:58 +00:00
⚠️ Rebuild chatbaloons
This commit is contained in:
parent
22f3aa4596
commit
cb3b7711d5
@ -1,7 +1,6 @@
|
|||||||
import { INitroPoint } from '@nitrots/nitro-renderer';
|
import { INitroPoint } from '@nitrots/nitro-renderer';
|
||||||
|
|
||||||
export class ChatBubbleMessage
|
export class ChatBubbleMessage {
|
||||||
{
|
|
||||||
public static BUBBLE_COUNTER: number = 0;
|
public static BUBBLE_COUNTER: number = 0;
|
||||||
|
|
||||||
public id: number = -1;
|
public id: number = -1;
|
||||||
@ -12,7 +11,7 @@ export class ChatBubbleMessage
|
|||||||
|
|
||||||
private _top: number = 0;
|
private _top: number = 0;
|
||||||
private _left: number = 0;
|
private _left: number = 0;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public senderId: number = -1,
|
public senderId: number = -1,
|
||||||
public senderCategory: number = -1,
|
public senderCategory: number = -1,
|
||||||
@ -25,35 +24,32 @@ export class ChatBubbleMessage
|
|||||||
public styleId: number = 0,
|
public styleId: number = 0,
|
||||||
public imageUrl: string = null,
|
public imageUrl: string = null,
|
||||||
public color: string = null,
|
public color: string = null,
|
||||||
public chatColours: string = ""
|
public chatColours: string = ""
|
||||||
)
|
) {
|
||||||
{
|
|
||||||
this.id = ++ChatBubbleMessage.BUBBLE_COUNTER;
|
this.id = ++ChatBubbleMessage.BUBBLE_COUNTER;
|
||||||
this.color = color;
|
this.color = color;
|
||||||
this.chatColours = chatColours;
|
this.chatColours = chatColours;
|
||||||
}
|
}
|
||||||
|
|
||||||
public get top(): number
|
public get top(): number {
|
||||||
{
|
|
||||||
return this._top;
|
return this._top;
|
||||||
}
|
}
|
||||||
|
|
||||||
public set top(value: number)
|
public set top(value: number) {
|
||||||
{
|
|
||||||
this._top = value;
|
this._top = value;
|
||||||
|
if (this.elementRef) {
|
||||||
if(this.elementRef) this.elementRef.style.top = (this._top + 'px');
|
this.elementRef.style.top = `${this._top}px`; // Always update DOM
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public get left(): number
|
public get left(): number {
|
||||||
{
|
|
||||||
return this._left;
|
return this._left;
|
||||||
}
|
}
|
||||||
|
|
||||||
public set left(value: number)
|
public set left(value: number) {
|
||||||
{
|
|
||||||
this._left = value;
|
this._left = value;
|
||||||
|
if (this.elementRef) {
|
||||||
if(this.elementRef) this.elementRef.style.left = (this._left + 'px');
|
this.elementRef.style.left = `${this._left}px`; // Always update DOM
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -3,25 +3,39 @@ import { FC, useEffect, useMemo, useRef, useState } from 'react';
|
|||||||
import { ChatBubbleMessage, GetRoomEngine } from '../../../../api';
|
import { ChatBubbleMessage, GetRoomEngine } from '../../../../api';
|
||||||
import { useOnClickChat } from '../../../../hooks';
|
import { useOnClickChat } from '../../../../hooks';
|
||||||
|
|
||||||
interface ChatWidgetMessageViewProps
|
interface ChatWidgetMessageViewProps {
|
||||||
{
|
|
||||||
chat: ChatBubbleMessage;
|
chat: ChatBubbleMessage;
|
||||||
makeRoom: (chat: ChatBubbleMessage) => void;
|
makeRoom: (chat: ChatBubbleMessage) => void;
|
||||||
bubbleWidth?: number;
|
bubbleWidth?: number;
|
||||||
selectedEmoji?: string;
|
selectedEmoji?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ChatWidgetMessageView: FC<ChatWidgetMessageViewProps> = props => {
|
export const ChatWidgetMessageView: FC<ChatWidgetMessageViewProps> = props => {
|
||||||
const { chat = null, makeRoom = null, bubbleWidth = RoomChatSettings.CHAT_BUBBLE_WIDTH_NORMAL, selectedEmoji } = props;
|
const { chat = null, makeRoom = null, bubbleWidth = RoomChatSettings.CHAT_BUBBLE_WIDTH_NORMAL, selectedEmoji } = props;
|
||||||
const [ isVisible, setIsVisible ] = useState(false);
|
const [isVisible, setIsVisible] = useState(false);
|
||||||
const [ isReady, setIsReady ] = useState<boolean>(false);
|
const [isReady, setIsReady] = useState<boolean>(false);
|
||||||
const { onClickChat = null } = useOnClickChat();
|
const { onClickChat = null } = useOnClickChat();
|
||||||
const elementRef = useRef<HTMLDivElement>();
|
const elementRef = useRef<HTMLDivElement>();
|
||||||
|
|
||||||
const getBubbleWidth = useMemo(() =>
|
// Memoize chat properties to prevent unnecessary re-renders
|
||||||
{
|
const chatMemo = useMemo(() => ({
|
||||||
switch(bubbleWidth)
|
id: chat?.id,
|
||||||
{
|
locationX: chat?.location?.x,
|
||||||
|
top: chat?.top,
|
||||||
|
left: chat?.left,
|
||||||
|
styleId: chat?.styleId,
|
||||||
|
color: chat?.color,
|
||||||
|
chatColours: chat?.chatColours,
|
||||||
|
username: chat?.username,
|
||||||
|
formattedText: chat?.formattedText,
|
||||||
|
imageUrl: chat?.imageUrl,
|
||||||
|
type: chat?.type,
|
||||||
|
roomId: chat?.roomId,
|
||||||
|
senderId: chat?.senderId
|
||||||
|
}), [chat?.id, chat?.location?.x, chat?.top, chat?.left, chat?.styleId, chat?.color, chat?.chatColours, chat?.username, chat?.formattedText, chat?.imageUrl, chat?.type, chat?.roomId, chat?.senderId]);
|
||||||
|
|
||||||
|
const getBubbleWidth = useMemo(() => {
|
||||||
|
switch (bubbleWidth) {
|
||||||
case RoomChatSettings.CHAT_BUBBLE_WIDTH_NORMAL:
|
case RoomChatSettings.CHAT_BUBBLE_WIDTH_NORMAL:
|
||||||
return 350;
|
return 350;
|
||||||
case RoomChatSettings.CHAT_BUBBLE_WIDTH_THIN:
|
case RoomChatSettings.CHAT_BUBBLE_WIDTH_THIN:
|
||||||
@ -29,15 +43,12 @@ export const ChatWidgetMessageView: FC<ChatWidgetMessageViewProps> = props => {
|
|||||||
case RoomChatSettings.CHAT_BUBBLE_WIDTH_WIDE:
|
case RoomChatSettings.CHAT_BUBBLE_WIDTH_WIDE:
|
||||||
return 2000;
|
return 2000;
|
||||||
}
|
}
|
||||||
}, [ bubbleWidth ]);
|
}, [bubbleWidth]);
|
||||||
|
|
||||||
useEffect(() =>
|
useEffect(() => {
|
||||||
{
|
|
||||||
setIsVisible(false);
|
setIsVisible(false);
|
||||||
|
|
||||||
const element = elementRef.current;
|
const element = elementRef.current;
|
||||||
|
if (!element) return;
|
||||||
if(!element) return;
|
|
||||||
|
|
||||||
const width = element.offsetWidth;
|
const width = element.offsetWidth;
|
||||||
const height = element.offsetHeight;
|
const height = element.offsetHeight;
|
||||||
@ -45,54 +56,67 @@ export const ChatWidgetMessageView: FC<ChatWidgetMessageViewProps> = props => {
|
|||||||
chat.width = width;
|
chat.width = width;
|
||||||
chat.height = height;
|
chat.height = height;
|
||||||
chat.elementRef = element;
|
chat.elementRef = element;
|
||||||
|
|
||||||
let left = chat.left;
|
let left = chat.left;
|
||||||
let top = chat.top;
|
let top = chat.top;
|
||||||
|
|
||||||
if(!left && !top)
|
// Calculate content offset based on styleId to account for margin-left in .chat-content
|
||||||
{
|
const contentOffset = chatMemo.styleId === 33 || chatMemo.styleId === 34 ? 35 : 27;
|
||||||
left = (chat.location.x - (width / 2));
|
|
||||||
top = (element.parentElement.offsetHeight - height);
|
// Position the bubble above the user (chat.location.x), adjusting for content offset
|
||||||
|
if (!left && !top) {
|
||||||
|
left = chatMemo.locationX ? (chatMemo.locationX - (width / 2) + contentOffset) : contentOffset;
|
||||||
|
top = element.parentElement ? (element.parentElement.offsetHeight - height) : 0;
|
||||||
chat.left = left;
|
chat.left = left;
|
||||||
chat.top = top;
|
chat.top = top;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apply position immediately
|
||||||
|
element.style.position = 'absolute';
|
||||||
|
element.style.left = `${left}px`;
|
||||||
|
element.style.top = `${top}px`;
|
||||||
|
|
||||||
setIsReady(true);
|
setIsReady(true);
|
||||||
|
|
||||||
return () =>
|
return () => {
|
||||||
{
|
|
||||||
chat.elementRef = null;
|
chat.elementRef = null;
|
||||||
|
|
||||||
setIsReady(false);
|
setIsReady(false);
|
||||||
}
|
};
|
||||||
}, [ chat ]);
|
}, [chat, chatMemo]);
|
||||||
|
|
||||||
useEffect(() =>
|
|
||||||
{
|
|
||||||
if(!isReady || !chat || isVisible) return;
|
|
||||||
|
|
||||||
if(makeRoom) makeRoom(chat);
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isReady || !chat || isVisible) return;
|
||||||
|
if (makeRoom) makeRoom(chat);
|
||||||
setIsVisible(true);
|
setIsVisible(true);
|
||||||
}, [ chat, isReady, isVisible, makeRoom ]);
|
}, [chat, isReady, isVisible, makeRoom]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={elementRef} className={`bubble-container newbubblehe ${isVisible ? 'visible' : 'invisible'}`} onClick={event => GetRoomEngine().selectRoomObject(chat.roomId, chat.senderId, RoomObjectCategory.UNIT)}>
|
<div
|
||||||
|
ref={elementRef}
|
||||||
|
className={`bubble-container newbubblehe ${isVisible ? 'visible' : 'invisible'}`}
|
||||||
|
onClick={event => GetRoomEngine().selectRoomObject(chatMemo.roomId, chatMemo.senderId, RoomObjectCategory.UNIT)}
|
||||||
|
>
|
||||||
{selectedEmoji && <span>{DOMPurify.sanitize(selectedEmoji)}</span>}
|
{selectedEmoji && <span>{DOMPurify.sanitize(selectedEmoji)}</span>}
|
||||||
{ (chat.styleId === 0) &&
|
{chatMemo.styleId === 0 && (
|
||||||
<div className="user-container-bg" style={ { backgroundColor: chat.color } } /> }
|
<div className="user-container-bg" style={{ backgroundColor: chatMemo.color }} />
|
||||||
<div className={ `chat-bubble bubble-${ chat.styleId } type-${ chat.type }` } style={ { maxWidth: getBubbleWidth } }>
|
)}
|
||||||
|
<div className={`chat-bubble bubble-${chatMemo.styleId} type-${chatMemo.type}`} style={{ maxWidth: getBubbleWidth }}>
|
||||||
<div className="user-container">
|
<div className="user-container">
|
||||||
{ chat.imageUrl && (chat.imageUrl.length > 0) &&
|
{chatMemo.imageUrl && chatMemo.imageUrl.length > 0 && (
|
||||||
<div className="user-image" style={ { backgroundImage: `url(${ chat.imageUrl })` } } /> }
|
<div className="user-image" style={{ backgroundImage: `url(${chatMemo.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: `${chatMemo.username}: ` }} />
|
||||||
<span className="message" style={{ color: chat.chatColours }} dangerouslySetInnerHTML={{ __html: `${chat.formattedText}` }} onClick={e => onClickChat(e)} />
|
<span
|
||||||
|
className="message"
|
||||||
|
style={{ color: chatMemo.chatColours }}
|
||||||
|
dangerouslySetInnerHTML={{ __html: `${chatMemo.formattedText}` }}
|
||||||
|
onClick={e => onClickChat(e)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="pointer" />
|
<div className="pointer" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
};
|
@ -1,8 +1,5 @@
|
|||||||
.nitro-chat-widget {
|
.nitro-chat-widget {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
top: 0;
|
top: 0;
|
||||||
min-height: 1px;
|
min-height: 1px;
|
||||||
@ -11,6 +8,7 @@
|
|||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-mention {
|
.chat-mention {
|
||||||
@ -28,17 +26,9 @@
|
|||||||
.bubble-container {
|
.bubble-container {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
transition: top 0.2s ease 0s;
|
|
||||||
user-select: none;
|
user-select: none;
|
||||||
pointer-events: all;
|
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 {
|
.user-container-bg {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: -1px;
|
top: -1px;
|
||||||
@ -74,14 +64,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&.type-0 {
|
&.type-0 {
|
||||||
// normal
|
|
||||||
.message {
|
.message {
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.type-1 {
|
&.type-1 {
|
||||||
// whisper
|
|
||||||
.message {
|
.message {
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
@ -90,7 +78,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&.type-2 {
|
&.type-2 {
|
||||||
// shout
|
|
||||||
.message {
|
.message {
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
@ -686,7 +673,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.bubble-39 {
|
&.bubble-39 {
|
||||||
border-image-source: url("@/assets/images/chat/chatbubbles/bubble_39.png");
|
border-image-source: url("@/assets/images/chat/chatbubbles/bubble_39.png");
|
||||||
|
|
||||||
border-image-slice: 16 6 7 32 fill;
|
border-image-slice: 16 6 7 32 fill;
|
||||||
@ -864,7 +851,7 @@
|
|||||||
.pointer {
|
.pointer {
|
||||||
background: url("@/assets/images/chat/chatbubbles/bubble_53_pointer.png");
|
background: url("@/assets/images/chat/chatbubbles/bubble_53_pointer.png");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-container {
|
.user-container {
|
||||||
z-index: 3;
|
z-index: 3;
|
||||||
@ -958,7 +945,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&.bubble-13 {
|
&.bubble-13 {
|
||||||
background-image: url('@/assets/images/chat/chatbubbles/bubble_13.png');
|
background-image: url('@/assets/images/chat/chatbubles/bubble_13.png');
|
||||||
}
|
}
|
||||||
|
|
||||||
&.bubble-14 {
|
&.bubble-14 {
|
||||||
@ -1096,8 +1083,8 @@
|
|||||||
background: url('@/assets/images/chat/chatbubbles/bubble_38_extra.png');
|
background: url('@/assets/images/chat/chatbubbles/bubble_38_extra.png');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.bubble-39 {
|
&.bubble-39 {
|
||||||
background-image: url("@/assets/images/chat/chatbubbles/bubble_39.png");
|
background-image: url("@/assets/images/chat/chatbubbles/bubble_39.png");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1148,12 +1135,12 @@
|
|||||||
&.bubble-51 {
|
&.bubble-51 {
|
||||||
background-image: url("@/assets/images/chat/chatbubbles/bubble_51.png");
|
background-image: url("@/assets/images/chat/chatbubbles/bubble_51.png");
|
||||||
}
|
}
|
||||||
|
|
||||||
&.bubble-52 {
|
&.bubble-52 {
|
||||||
background-image: url("@/assets/images/chat/chatbubbles/bubble_52.png");
|
background-image: url("@/assets/images/chat/chatbubbles/bubble_52.png");
|
||||||
}
|
}
|
||||||
|
|
||||||
&.bubble-53 {
|
&.bubble-53 {
|
||||||
background-image: url("@/assets/images/chat/chatbubbles/bubble_53.png");
|
background-image: url("@/assets/images/chat/chatbubbles/bubble_53.png");
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -6,156 +6,161 @@ import IntervalWebWorker from '../../../../workers/IntervalWebWorker';
|
|||||||
import { WorkerBuilder } from '../../../../workers/WorkerBuilder';
|
import { WorkerBuilder } from '../../../../workers/WorkerBuilder';
|
||||||
import { ChatWidgetMessageView } from './ChatWidgetMessageView';
|
import { ChatWidgetMessageView } from './ChatWidgetMessageView';
|
||||||
|
|
||||||
export const ChatWidgetView: FC<{}> = props =>
|
export const ChatWidgetView: FC<{}> = props => {
|
||||||
{
|
|
||||||
const { chatMessages = [], setChatMessages = null, chatSettings = null, getScrollSpeed = 6000 } = useChatWidget();
|
const { chatMessages = [], setChatMessages = null, chatSettings = null, getScrollSpeed = 6000 } = useChatWidget();
|
||||||
const elementRef = useRef<HTMLDivElement>();
|
const elementRef = useRef<HTMLDivElement>();
|
||||||
|
const lastTickTimeRef = useRef<number>(Date.now());
|
||||||
|
const scrollAmountPerTick = 15; // Pixels moved per tick
|
||||||
|
const workerRef = useRef<Worker | null>(null); // Preserve worker across re-renders
|
||||||
|
|
||||||
const removeHiddenChats = useCallback(() =>
|
const removeHiddenChats = useCallback(() => {
|
||||||
{
|
setChatMessages(prevValue => {
|
||||||
setChatMessages(prevValue =>
|
if (prevValue) {
|
||||||
{
|
|
||||||
if(prevValue)
|
|
||||||
{
|
|
||||||
const newMessages = prevValue.filter(chat => ((chat.top > (-(chat.height) * 2))));
|
const newMessages = prevValue.filter(chat => ((chat.top > (-(chat.height) * 2))));
|
||||||
|
if (newMessages.length !== prevValue.length) return newMessages;
|
||||||
if(newMessages.length !== prevValue.length) return newMessages;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return prevValue;
|
return prevValue;
|
||||||
})
|
});
|
||||||
}, [ setChatMessages ]);
|
}, [setChatMessages]);
|
||||||
|
|
||||||
const checkOverlappingChats = useCallback((chat: ChatBubbleMessage, moved: number, tempChats: ChatBubbleMessage[]) =>
|
const checkOverlappingChats = useCallback((chat: ChatBubbleMessage, moved: number, tempChats: ChatBubbleMessage[]) => {
|
||||||
{
|
for (let i = (chatMessages.indexOf(chat) - 1); i >= 0; i--) {
|
||||||
for(let i = (chatMessages.indexOf(chat) - 1); i >= 0; i--)
|
|
||||||
{
|
|
||||||
const collides = chatMessages[i];
|
const collides = chatMessages[i];
|
||||||
|
if (!collides || (chat === collides) || (tempChats.indexOf(collides) >= 0) || (((collides.top + collides.height) - moved) > (chat.top + chat.height))) continue;
|
||||||
|
|
||||||
if(!collides || (chat === collides) || (tempChats.indexOf(collides) >= 0) || (((collides.top + collides.height) - moved) > (chat.top + chat.height))) continue;
|
if (DoChatsOverlap(chat, collides, -moved, 0)) {
|
||||||
|
|
||||||
if(DoChatsOverlap(chat, collides, -moved, 0))
|
|
||||||
{
|
|
||||||
const amount = Math.abs((collides.top + collides.height) - chat.top);
|
const amount = Math.abs((collides.top + collides.height) - chat.top);
|
||||||
|
|
||||||
tempChats.push(collides);
|
tempChats.push(collides);
|
||||||
|
|
||||||
collides.top -= amount;
|
collides.top -= amount;
|
||||||
collides.skipMovement = true;
|
|
||||||
|
|
||||||
checkOverlappingChats(collides, amount, tempChats);
|
checkOverlappingChats(collides, amount, tempChats);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [ chatMessages ]);
|
}, [chatMessages]);
|
||||||
|
|
||||||
const makeRoom = useCallback((chat: ChatBubbleMessage) =>
|
|
||||||
{
|
|
||||||
if(chatSettings.mode === RoomChatSettings.CHAT_MODE_FREE_FLOW)
|
|
||||||
{
|
|
||||||
chat.skipMovement = true;
|
|
||||||
|
|
||||||
checkOverlappingChats(chat, 0, [ chat ]);
|
|
||||||
|
|
||||||
|
const makeRoom = useCallback((chat: ChatBubbleMessage) => {
|
||||||
|
if (chatSettings.mode === RoomChatSettings.CHAT_MODE_FREE_FLOW) {
|
||||||
|
checkOverlappingChats(chat, 0, [chat]);
|
||||||
removeHiddenChats();
|
removeHiddenChats();
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
const lowestPoint = (chat.top + chat.height);
|
const lowestPoint = (chat.top + chat.height);
|
||||||
const requiredSpace = chat.height;
|
const requiredSpace = chat.height;
|
||||||
const spaceAvailable = (elementRef.current.offsetHeight - lowestPoint);
|
const spaceAvailable = (elementRef.current.offsetHeight - lowestPoint);
|
||||||
const amount = (requiredSpace - spaceAvailable);
|
const amount = (requiredSpace - spaceAvailable);
|
||||||
|
|
||||||
if(spaceAvailable < requiredSpace)
|
if (spaceAvailable < requiredSpace) {
|
||||||
{
|
setChatMessages(prevValue => {
|
||||||
setChatMessages(prevValue =>
|
prevValue.forEach(prevChat => {
|
||||||
{
|
|
||||||
prevValue.forEach(prevChat =>
|
|
||||||
{
|
|
||||||
if(prevChat === chat) return;
|
|
||||||
|
|
||||||
prevChat.top -= amount;
|
prevChat.top -= amount;
|
||||||
|
if (prevChat.elementRef) {
|
||||||
|
prevChat.elementRef.style.top = `${prevChat.top}px`;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return prevValue;
|
return prevValue;
|
||||||
});
|
});
|
||||||
|
|
||||||
removeHiddenChats();
|
removeHiddenChats();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [ chatSettings, checkOverlappingChats, removeHiddenChats, setChatMessages ]);
|
}, [chatSettings, checkOverlappingChats, removeHiddenChats, setChatMessages]);
|
||||||
|
|
||||||
useEffect(() =>
|
const moveAllChatsUp = useCallback((amount: number) => {
|
||||||
{
|
setChatMessages(prevValue => {
|
||||||
const resize = (event: UIEvent = null) =>
|
prevValue.forEach(chat => {
|
||||||
{
|
chat.top -= amount;
|
||||||
if(!elementRef || !elementRef.current) return;
|
if (chat.elementRef) {
|
||||||
|
chat.elementRef.style.top = `${chat.top}px`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return prevValue;
|
||||||
|
});
|
||||||
|
removeHiddenChats();
|
||||||
|
lastTickTimeRef.current = Date.now();
|
||||||
|
}, [setChatMessages, removeHiddenChats]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const resize = (event: UIEvent = null) => {
|
||||||
|
if (!elementRef || !elementRef.current) return;
|
||||||
|
|
||||||
const currentHeight = elementRef.current.offsetHeight;
|
const currentHeight = elementRef.current.offsetHeight;
|
||||||
const newHeight = Math.round(document.body.offsetHeight * GetConfiguration<number>('chat.viewer.height.percentage'));
|
const newHeight = Math.round(document.body.offsetHeight * GetConfiguration<number>('chat.viewer.height.percentage'));
|
||||||
|
|
||||||
elementRef.current.style.height = `${ newHeight }px`;
|
elementRef.current.style.height = `${newHeight}px`;
|
||||||
|
|
||||||
setChatMessages(prevValue =>
|
setChatMessages(prevValue => {
|
||||||
{
|
if (prevValue) {
|
||||||
if(prevValue)
|
prevValue.forEach(chat => {
|
||||||
{
|
chat.top -= (currentHeight - newHeight);
|
||||||
prevValue.forEach(chat => (chat.top -= (currentHeight - newHeight)));
|
if (chat.elementRef) {
|
||||||
|
chat.elementRef.style.top = `${chat.top}px`;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return prevValue;
|
return prevValue;
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
window.addEventListener('resize', resize);
|
window.addEventListener('resize', resize);
|
||||||
|
|
||||||
resize();
|
resize();
|
||||||
|
|
||||||
return () =>
|
return () => {
|
||||||
{
|
|
||||||
window.removeEventListener('resize', resize);
|
window.removeEventListener('resize', resize);
|
||||||
}
|
};
|
||||||
}, [ setChatMessages ]);
|
}, [setChatMessages]);
|
||||||
|
|
||||||
useEffect(() =>
|
|
||||||
{
|
|
||||||
const moveAllChatsUp = (amount: number) =>
|
|
||||||
{
|
|
||||||
setChatMessages(prevValue =>
|
|
||||||
{
|
|
||||||
prevValue.forEach(chat =>
|
|
||||||
{
|
|
||||||
if(chat.skipMovement)
|
|
||||||
{
|
|
||||||
chat.skipMovement = false;
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
chat.top -= amount;
|
|
||||||
});
|
|
||||||
|
|
||||||
return prevValue;
|
|
||||||
});
|
|
||||||
|
|
||||||
removeHiddenChats();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
const worker = new WorkerBuilder(IntervalWebWorker);
|
const worker = new WorkerBuilder(IntervalWebWorker);
|
||||||
|
workerRef.current = worker;
|
||||||
worker.onmessage = () => moveAllChatsUp(15);
|
worker.onmessage = () => moveAllChatsUp(scrollAmountPerTick);
|
||||||
|
|
||||||
worker.postMessage({ action: 'START', content: getScrollSpeed });
|
worker.postMessage({ action: 'START', content: getScrollSpeed });
|
||||||
|
|
||||||
return () =>
|
return () => {
|
||||||
{
|
if (workerRef.current) {
|
||||||
worker.postMessage({ action: 'STOP' });
|
workerRef.current.postMessage({ action: 'STOP' });
|
||||||
worker.terminate();
|
workerRef.current.terminate();
|
||||||
|
workerRef.current = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [moveAllChatsUp]); // Remove getScrollSpeed from deps to prevent re-creation
|
||||||
|
|
||||||
|
// Update worker interval if getScrollSpeed changes
|
||||||
|
useEffect(() => {
|
||||||
|
if (workerRef.current) {
|
||||||
|
workerRef.current.postMessage({ action: 'UPDATE', content: getScrollSpeed });
|
||||||
}
|
}
|
||||||
}, [ getScrollSpeed, removeHiddenChats, setChatMessages ]);
|
}, [getScrollSpeed]);
|
||||||
|
|
||||||
|
// Handle new chats and ensure immediate scrolling
|
||||||
|
useEffect(() => {
|
||||||
|
if (!chatMessages.length) return;
|
||||||
|
|
||||||
|
const lastChat = chatMessages[chatMessages.length - 1];
|
||||||
|
const timeSinceLastTick = Date.now() - lastTickTimeRef.current;
|
||||||
|
const ticksMissed = Math.floor(timeSinceLastTick / getScrollSpeed);
|
||||||
|
const offset = ticksMissed * scrollAmountPerTick;
|
||||||
|
|
||||||
|
setChatMessages(prevValue => {
|
||||||
|
const newChat = prevValue[prevValue.length - 1];
|
||||||
|
if (newChat === lastChat) {
|
||||||
|
newChat.top -= offset;
|
||||||
|
if (newChat.elementRef) {
|
||||||
|
newChat.elementRef.style.top = `${newChat.top}px`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return prevValue;
|
||||||
|
});
|
||||||
|
|
||||||
|
moveAllChatsUp(scrollAmountPerTick);
|
||||||
|
}, [chatMessages, getScrollSpeed, moveAllChatsUp]);
|
||||||
|
|
||||||
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 } bubbleWidth={ chatSettings.weight } />) }
|
{chatMessages.map(chat => (
|
||||||
|
<ChatWidgetMessageView
|
||||||
|
key={chat.id}
|
||||||
|
chat={chat}
|
||||||
|
makeRoom={makeRoom}
|
||||||
|
bubbleWidth={chatSettings.weight}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
};
|
@ -40,8 +40,6 @@ const useChatWidgetState = () =>
|
|||||||
|
|
||||||
const setFigureImage = (figure: string, username: string): Promise<string | null> => {
|
const setFigureImage = (figure: string, username: string): Promise<string | null> => {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
console.log('setFigureImage called with figure:', figure, 'username:', username);
|
|
||||||
|
|
||||||
const avatarImage = GetAvatarRenderManager().createAvatarImage(figure, AvatarScaleType.LARGE, null, {
|
const avatarImage = GetAvatarRenderManager().createAvatarImage(figure, AvatarScaleType.LARGE, null, {
|
||||||
resetFigure: figure => {
|
resetFigure: figure => {
|
||||||
if (isDisposed.current) return;
|
if (isDisposed.current) return;
|
||||||
@ -51,37 +49,27 @@ const useChatWidgetState = () =>
|
|||||||
disposed: false
|
disposed: false
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('avatarImage result:', avatarImage);
|
|
||||||
|
|
||||||
if (!avatarImage) {
|
if (!avatarImage) {
|
||||||
console.log('Failed to create avatarImage for figure:', figure);
|
|
||||||
resolve('https://via.placeholder.com/40');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
avatarImage.getCroppedImage(AvatarSetType.HEAD).then(image => {
|
avatarImage.getCroppedImage(AvatarSetType.HEAD).then(image => {
|
||||||
console.log('Cropped image:', image, 'Image src:', image?.src);
|
|
||||||
|
|
||||||
if (!image || !image.src) {
|
if (!image || !image.src) {
|
||||||
console.log('Failed to get cropped image or src for figure:', figure);
|
|
||||||
avatarImage.dispose();
|
avatarImage.dispose();
|
||||||
resolve('https://via.placeholder.com/40');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const color = avatarImage.getPartColor(AvatarFigurePartType.CHEST);
|
const color = avatarImage.getPartColor(AvatarFigurePartType.CHEST);
|
||||||
console.log('Avatar color:', color, 'RGB:', color?.rgb);
|
|
||||||
|
|
||||||
avatarColorCache.set(figure, ((color && color.rgb) || 16777215));
|
avatarColorCache.set(figure, ((color && color.rgb) || 16777215));
|
||||||
avatarImageCache.set(figure, image.src);
|
avatarImageCache.set(figure, image.src);
|
||||||
console.log('Cached image src:', image.src);
|
|
||||||
|
|
||||||
// Update existing chat messages for this username
|
|
||||||
setChatMessages(prevValue => {
|
setChatMessages(prevValue => {
|
||||||
const updatedMessages = prevValue.map(chat => {
|
const updatedMessages = prevValue.map(chat => {
|
||||||
if (chat.username === username && chat.imageUrl !== image.src) {
|
if (chat.username === username && chat.imageUrl !== image.src) {
|
||||||
chat.imageUrl = image.src; // Update in-place
|
chat.imageUrl = image.src;
|
||||||
return { ...chat }; // Shallow copy to trigger re-render
|
return { ...chat };
|
||||||
}
|
}
|
||||||
return chat;
|
return chat;
|
||||||
});
|
});
|
||||||
@ -91,9 +79,7 @@ const useChatWidgetState = () =>
|
|||||||
avatarImage.dispose();
|
avatarImage.dispose();
|
||||||
resolve(image.src);
|
resolve(image.src);
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
console.error('Error in setFigureImage:', error);
|
|
||||||
avatarImage.dispose();
|
avatarImage.dispose();
|
||||||
resolve('https://via.placeholder.com/40');
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -105,7 +91,7 @@ const useChatWidgetState = () =>
|
|||||||
setFigureImage(figure, username).then(src => {
|
setFigureImage(figure, username).then(src => {
|
||||||
avatarImageCache.set(figure, src);
|
avatarImageCache.set(figure, src);
|
||||||
});
|
});
|
||||||
return 'https://via.placeholder.com/40';
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return existing;
|
return existing;
|
||||||
@ -152,14 +138,6 @@ const useChatWidgetState = () =>
|
|||||||
|
|
||||||
const figure = userData.figure;
|
const figure = userData.figure;
|
||||||
|
|
||||||
console.log('Chat Event Debug:', {
|
|
||||||
userId: event.objectId,
|
|
||||||
username: userData.name,
|
|
||||||
userType,
|
|
||||||
figure,
|
|
||||||
roomObjectExists: !!roomObject
|
|
||||||
});
|
|
||||||
|
|
||||||
switch(userType)
|
switch(userType)
|
||||||
{
|
{
|
||||||
case RoomObjectType.PET:
|
case RoomObjectType.PET:
|
||||||
@ -169,7 +147,6 @@ const useChatWidgetState = () =>
|
|||||||
break;
|
break;
|
||||||
case RoomObjectType.USER:
|
case RoomObjectType.USER:
|
||||||
imageUrl = getUserImage(figure, userData.name);
|
imageUrl = getUserImage(figure, userData.name);
|
||||||
console.log('getUserImage result:', { figure, imageUrl });
|
|
||||||
break;
|
break;
|
||||||
case RoomObjectType.RENTABLE_BOT:
|
case RoomObjectType.RENTABLE_BOT:
|
||||||
case RoomObjectType.BOT:
|
case RoomObjectType.BOT:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user