🆙 Init V3

This commit is contained in:
DuckieTM
2026-01-31 09:10:52 +01:00
commit 7feb10ab15
1733 changed files with 53405 additions and 0 deletions
@@ -0,0 +1,99 @@
import { GetRoomEngine, RoomChatSettings, RoomObjectCategory } from '@nitrots/nitro-renderer';
import { FC, useEffect, useMemo, useRef, useState } from 'react';
import { ChatBubbleMessage } from '../../../../api';
interface ChatWidgetMessageViewProps
{
chat: ChatBubbleMessage;
makeRoom: (chat: ChatBubbleMessage) => void;
bubbleWidth?: number;
}
export const ChatWidgetMessageView: FC<ChatWidgetMessageViewProps> = ({
chat = null,
makeRoom = null,
bubbleWidth = RoomChatSettings.CHAT_BUBBLE_WIDTH_NORMAL
}) =>
{
const [ isVisible, setIsVisible ] = useState(false);
const [ isReady, setIsReady ] = useState(false);
const elementRef = useRef<HTMLDivElement>(null);
const getBubbleWidth = useMemo(() =>
{
switch(bubbleWidth)
{
case RoomChatSettings.CHAT_BUBBLE_WIDTH_NORMAL:
return 'w-350';
case RoomChatSettings.CHAT_BUBBLE_WIDTH_THIN:
return 'w-240';
case RoomChatSettings.CHAT_BUBBLE_WIDTH_WIDE:
return 'w-2000';
default:
return 'w-350';
}
}, [ bubbleWidth ]);
useEffect(() =>
{
setIsVisible(false);
const element = elementRef.current;
if(!element) return;
const { offsetWidth: width, offsetHeight: height } = element;
chat.width = width;
chat.height = height;
chat.elementRef = element;
let { left, top } = chat;
if(!left && !top)
{
left = (chat.location.x - (width / 2));
top = (element.parentElement.offsetHeight - height);
chat.left = left;
chat.top = top;
}
setIsReady(true);
return () =>
{
chat.elementRef = null;
setIsReady(false);
};
}, [ chat ]);
useEffect(() =>
{
if(!isReady || !chat || isVisible) return;
if(makeRoom) makeRoom(chat);
setIsVisible(true);
}, [ chat, isReady, isVisible, makeRoom ]);
return (
<div ref={ elementRef } className={ `bubble-container newbubblehe ${ isVisible ? 'visible' : 'invisible' } w-max absolute select-none pointer-events-auto` }
onClick={ () => GetRoomEngine().selectRoomObject(chat.roomId, chat.senderId, RoomObjectCategory.UNIT) }>
{ chat.styleId === 0 && (
<div className="absolute top-[-1px] left-[1px] w-[30px] h-[calc(100%-0.5px)] rounded-[7px] z-[1]" style={ { backgroundColor: chat.color } } />
) }
<div className={ `chat-bubble bubble-${ chat.styleId } ${ getBubbleWidth } relative z-[1] break-words min-h-[26px] text-[14px] max-w-[350px]` }
style={ { maxWidth: getBubbleWidth } }>
<div className="user-container flex items-center justify-center h-full max-h-[24px] overflow-hidden">
{ chat.imageUrl && chat.imageUrl.length > 0 && (
<div className="user-image absolute top-[-15px] left-[-9.25px] w-[45px] h-[65px] bg-no-repeat bg-center scale-50" style={ { backgroundImage: `url(${ chat.imageUrl })` } } />
) }
</div>
<div className="chat-content py-[5px] px-[6px] ml-[27px] leading-[1] min-h-[25px]">
<b className="username" dangerouslySetInnerHTML={ { __html: `${ chat.username }: ` } } />
<span className="message" dangerouslySetInnerHTML={ { __html: `${ chat.formattedText }` } } />
</div>
<div className="pointer absolute left-[50%] translate-x-[-50%] w-[9px] h-[6px] bottom-[-5px]" />
</div>
</div>
);
};
@@ -0,0 +1,162 @@
import { RoomChatSettings } from '@nitrots/nitro-renderer';
import { FC, useCallback, useEffect, useRef } from 'react';
import { ChatBubbleMessage, DoChatsOverlap, GetConfigurationValue } from '../../../../api';
import { useChatWidget } from '../../../../hooks';
import IntervalWebWorker from '../../../../workers/IntervalWebWorker';
import { WorkerBuilder } from '../../../../workers/WorkerBuilder';
import { ChatWidgetMessageView } from './ChatWidgetMessageView';
export const ChatWidgetView: FC<{}> = props =>
{
const { chatMessages = [], setChatMessages = null, chatSettings = null, getScrollSpeed = 6000 } = useChatWidget();
const elementRef = useRef<HTMLDivElement>();
const removeHiddenChats = useCallback(() =>
{
setChatMessages(prevValue =>
{
if(prevValue)
{
const newMessages = prevValue.filter(chat => ((chat.top > (-(chat.height) * 2))));
if(newMessages.length !== prevValue.length) return newMessages;
}
return prevValue;
});
}, [ setChatMessages ]);
const checkOverlappingChats = useCallback((chat: ChatBubbleMessage, moved: number, tempChats: ChatBubbleMessage[]) =>
{
for(let i = (chatMessages.indexOf(chat) - 1); i >= 0; i--)
{
const collides = chatMessages[i];
if(!collides || (chat === collides) || (tempChats.indexOf(collides) >= 0) || (((collides.top + collides.height) - moved) > (chat.top + chat.height))) continue;
if(DoChatsOverlap(chat, collides, -moved, 0))
{
const amount = Math.abs((collides.top + collides.height) - chat.top);
tempChats.push(collides);
collides.top -= amount;
collides.skipMovement = true;
checkOverlappingChats(collides, amount, tempChats);
}
}
}, [ chatMessages ]);
const makeRoom = useCallback((chat: ChatBubbleMessage) =>
{
if(chatSettings.mode === RoomChatSettings.CHAT_MODE_FREE_FLOW)
{
chat.skipMovement = true;
checkOverlappingChats(chat, 0, [ chat ]);
removeHiddenChats();
}
else
{
const lowestPoint = (chat.top + chat.height);
const requiredSpace = chat.height;
const spaceAvailable = (elementRef.current.offsetHeight - lowestPoint);
const amount = (requiredSpace - spaceAvailable);
if(spaceAvailable < requiredSpace)
{
setChatMessages(prevValue =>
{
prevValue.forEach(prevChat =>
{
if(prevChat === chat) return;
prevChat.top -= amount;
});
return prevValue;
});
removeHiddenChats();
}
}
}, [ chatSettings, checkOverlappingChats, removeHiddenChats, setChatMessages ]);
useEffect(() =>
{
const resize = (event: UIEvent = null) =>
{
if(!elementRef || !elementRef.current) return;
const currentHeight = elementRef.current.offsetHeight;
const newHeight = Math.round(document.body.offsetHeight * GetConfigurationValue<number>('chat.viewer.height.percentage'));
elementRef.current.style.height = `${ newHeight }px`;
setChatMessages(prevValue =>
{
if(prevValue)
{
prevValue.forEach(chat => (chat.top -= (currentHeight - newHeight)));
}
return prevValue;
});
};
window.addEventListener('resize', resize);
resize();
return () =>
{
window.removeEventListener('resize', resize);
};
}, [ setChatMessages ]);
useEffect(() =>
{
const moveAllChatsUp = (amount: number) =>
{
setChatMessages(prevValue =>
{
prevValue.forEach(chat =>
{
if(chat.skipMovement)
{
chat.skipMovement = false;
return;
}
chat.top -= amount;
});
return prevValue;
});
removeHiddenChats();
};
const worker = new WorkerBuilder(IntervalWebWorker);
worker.onmessage = () => moveAllChatsUp(15);
worker.postMessage({ action: 'START', content: getScrollSpeed });
return () =>
{
worker.postMessage({ action: 'STOP' });
worker.terminate();
};
}, [ getScrollSpeed, removeHiddenChats, setChatMessages ]);
return (
<div ref={ elementRef } className="absolute flex justify-center items-center w-full top-0 min-h-[1px] z-[var(--chat-zindex)] bg-transparent roundehidden shadow-none pointer-events-none">
{ chatMessages.map(chat => <ChatWidgetMessageView key={ chat.id } bubbleWidth={ chatSettings.weight } chat={ chat } makeRoom={ makeRoom } />) }
</div>
);
};