mirror of
https://github.com/duckietm/Nitro-Cool-UI.git
synced 2025-06-21 22:36:58 +00:00
⚠️ Chat has been completed
This commit is contained in:
parent
cb3b7711d5
commit
68bd7ed148
@ -1,4 +1,4 @@
|
||||
import { ILinkEventTracker } from '@nitrots/nitro-renderer';
|
||||
import { ILinkEventTracker, RoomObjectType } from '@nitrots/nitro-renderer';
|
||||
import { FC, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { AddEventLinkTracker, ChatEntryType, LocalizeText, RemoveLinkEventTracker } from '../../api';
|
||||
import { Column, Flex, InfiniteScroll, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text, Button } from '../../common';
|
||||
@ -87,6 +87,17 @@ export const ChatHistoryView: FC<{}> = props =>
|
||||
scrollToBottom={true}
|
||||
rowRender={row =>
|
||||
{
|
||||
// Define styles where imageUrl should not be loaded
|
||||
const stylesWithoutImage = [1, 2, 8, 28, 30, 32, 33, 34, 35, 36, 38];
|
||||
const shouldDisplayImage = !stylesWithoutImage.includes(row.style);
|
||||
|
||||
// Determine image dimensions and positioning based on entityType
|
||||
const isPet = row.entityType === RoomObjectType.PET;
|
||||
const imageWidth = isPet ? '41px' : '90px'; // Double for users to account for transform: scale(0.5)
|
||||
const imageHeight = isPet ? '54px' : '130px'; // Double for users to account for transform: scale(0.5)
|
||||
const imageTop = isPet ? '-15px' : '-50px'; // Adjusted to shift user image up
|
||||
const imageLeft = isPet ? '-9.25px' : '-31px'; // Adjusted to shift user image left
|
||||
|
||||
return (
|
||||
<Flex alignItems="center" className="p-1" gap={2}>
|
||||
<Text variant="muted">{row.timestamp}</Text>
|
||||
@ -96,8 +107,19 @@ export const ChatHistoryView: FC<{}> = props =>
|
||||
<div className="user-container-bg" style={{ backgroundColor: row.color }} /> }
|
||||
<div className={`chat-bubble bubble-${row.style} type-${row.chatType}`} style={{ maxWidth: '100%' }}>
|
||||
<div className="user-container">
|
||||
{ row.imageUrl && (row.imageUrl.length > 0) &&
|
||||
<div className="user-image" style={{ backgroundImage: `url(${row.imageUrl})` }} /> }
|
||||
{ shouldDisplayImage && row.imageUrl && (row.imageUrl.length > 0) &&
|
||||
<div
|
||||
className="user-image"
|
||||
style={{
|
||||
backgroundImage: `url(${row.imageUrl})`,
|
||||
width: imageWidth,
|
||||
height: imageHeight,
|
||||
top: imageTop,
|
||||
left: imageLeft,
|
||||
backgroundSize: 'cover',
|
||||
backgroundPosition: 'center',
|
||||
}}
|
||||
/> }
|
||||
</div>
|
||||
<div className="chat-content">
|
||||
<b className="username mr-1" dangerouslySetInnerHTML={{ __html: `${row.name}: ` }} />
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { RoomChatSettings, RoomObjectCategory } from '@nitrots/nitro-renderer';
|
||||
import { RoomChatSettings, RoomObjectCategory, RoomObjectType } from '@nitrots/nitro-renderer';
|
||||
import { FC, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { ChatBubbleMessage, GetRoomEngine } from '../../../../api';
|
||||
import { useOnClickChat } from '../../../../hooks';
|
||||
@ -17,7 +17,6 @@ export const ChatWidgetMessageView: FC<ChatWidgetMessageViewProps> = props => {
|
||||
const { onClickChat = null } = useOnClickChat();
|
||||
const elementRef = useRef<HTMLDivElement>();
|
||||
|
||||
// Memoize chat properties to prevent unnecessary re-renders
|
||||
const chatMemo = useMemo(() => ({
|
||||
id: chat?.id,
|
||||
locationX: chat?.location?.x,
|
||||
@ -31,8 +30,9 @@ export const ChatWidgetMessageView: FC<ChatWidgetMessageViewProps> = props => {
|
||||
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]);
|
||||
senderId: chat?.senderId,
|
||||
userType: (chat as any)?.userType || 0
|
||||
}), [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, (chat as any)?.userType]);
|
||||
|
||||
const getBubbleWidth = useMemo(() => {
|
||||
switch (bubbleWidth) {
|
||||
@ -60,10 +60,8 @@ export const ChatWidgetMessageView: FC<ChatWidgetMessageViewProps> = props => {
|
||||
let left = chat.left;
|
||||
let top = chat.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;
|
||||
|
||||
// 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;
|
||||
@ -71,7 +69,6 @@ export const ChatWidgetMessageView: FC<ChatWidgetMessageViewProps> = props => {
|
||||
chat.top = top;
|
||||
}
|
||||
|
||||
// Apply position immediately
|
||||
element.style.position = 'absolute';
|
||||
element.style.left = `${left}px`;
|
||||
element.style.top = `${top}px`;
|
||||
@ -90,6 +87,16 @@ export const ChatWidgetMessageView: FC<ChatWidgetMessageViewProps> = props => {
|
||||
setIsVisible(true);
|
||||
}, [chat, isReady, isVisible, makeRoom]);
|
||||
|
||||
const isPet = chatMemo.userType === RoomObjectType.PET;
|
||||
const imageWidth = isPet ? '41px' : '90px';
|
||||
const imageHeight = isPet ? '54px' : '130px';
|
||||
const imageTop = isPet ? '-15px' : '-50px';
|
||||
const imageLeft = isPet ? '-9.25px' : '-31px';
|
||||
|
||||
// Define styles where imageUrl should not be loaded
|
||||
const stylesWithoutImage = [1, 2, 8, 28, 30, 32, 33, 34, 35, 36, 38];
|
||||
const shouldDisplayImage = !stylesWithoutImage.includes(chatMemo.styleId);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={elementRef}
|
||||
@ -101,11 +108,23 @@ export const ChatWidgetMessageView: FC<ChatWidgetMessageViewProps> = props => {
|
||||
<div className="user-container-bg" style={{ backgroundColor: chatMemo.color }} />
|
||||
)}
|
||||
<div className={`chat-bubble bubble-${chatMemo.styleId} type-${chatMemo.type}`} style={{ maxWidth: getBubbleWidth }}>
|
||||
<div className="user-container">
|
||||
{chatMemo.imageUrl && chatMemo.imageUrl.length > 0 && (
|
||||
<div className="user-image" style={{ backgroundImage: `url(${chatMemo.imageUrl})` }} />
|
||||
)}
|
||||
</div>
|
||||
{shouldDisplayImage && chatMemo.imageUrl && typeof chatMemo.imageUrl === 'string' && chatMemo.imageUrl.length > 0 && (
|
||||
<div className="user-container" style={{ display: 'block' }}>
|
||||
<div
|
||||
className="user-image"
|
||||
style={{
|
||||
backgroundImage: `url(${chatMemo.imageUrl})`,
|
||||
width: imageWidth,
|
||||
height: imageHeight,
|
||||
top: imageTop,
|
||||
left: imageLeft,
|
||||
backgroundSize: 'cover',
|
||||
backgroundPosition: 'center',
|
||||
}}
|
||||
onError={() => console.log(`Failed to load image for Chat ID ${chatMemo.id}: ${chatMemo.imageUrl}`)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="chat-content">
|
||||
<b className="username mr-1" dangerouslySetInnerHTML={{ __html: `${chatMemo.username}: ` }} />
|
||||
<span
|
||||
|
@ -10,8 +10,8 @@ export const ChatWidgetView: FC<{}> = props => {
|
||||
const { chatMessages = [], setChatMessages = null, chatSettings = null, getScrollSpeed = 6000 } = useChatWidget();
|
||||
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 scrollAmountPerTick = 15;
|
||||
const workerRef = useRef<Worker | null>(null);
|
||||
|
||||
const removeHiddenChats = useCallback(() => {
|
||||
setChatMessages(prevValue => {
|
||||
@ -119,16 +119,14 @@ export const ChatWidgetView: FC<{}> = props => {
|
||||
workerRef.current = null;
|
||||
}
|
||||
};
|
||||
}, [moveAllChatsUp]); // Remove getScrollSpeed from deps to prevent re-creation
|
||||
}, [moveAllChatsUp]);
|
||||
|
||||
// Update worker interval if getScrollSpeed changes
|
||||
useEffect(() => {
|
||||
if (workerRef.current) {
|
||||
workerRef.current.postMessage({ action: 'UPDATE', content: getScrollSpeed });
|
||||
}
|
||||
}, [getScrollSpeed]);
|
||||
|
||||
// Handle new chats and ensure immediate scrolling
|
||||
useEffect(() => {
|
||||
if (!chatMessages.length) return;
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { AvatarFigurePartType, AvatarScaleType, AvatarSetType, GetGuestRoomResultEvent, NitroPoint, PetFigureData, RoomChatSettings, RoomChatSettingsEvent, RoomDragEvent, RoomObjectCategory, RoomObjectType, RoomObjectVariable, RoomSessionChatEvent, RoomUserData, SystemChatStyleEnum, TextureUtils, Vector3d } from '@nitrots/nitro-renderer';
|
||||
import { AvatarFigurePartType, AvatarScaleType, AvatarSetType, GetGuestRoomResultEvent, NitroPoint, PetFigureData, RoomChatSettings, RoomChatSettingsEvent, RoomDragEvent, RoomObjectCategory, RoomObjectType, RoomObjectVariable, RoomSessionChatEvent, SystemChatStyleEnum, TextureUtils, Vector3d } from '@nitrots/nitro-renderer';
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { ChatBubbleMessage, ChatEntryType, ChatHistoryCurrentDate, GetAvatarRenderManager, GetConfiguration, GetRoomEngine, GetRoomObjectScreenLocation, IRoomChatSettings, LocalizeText, PlaySound, RoomChatFormatter } from '../../../api';
|
||||
import { useMessageEvent, useRoomEngineEvent, useRoomSessionManagerEvent } from '../../events';
|
||||
@ -50,13 +50,14 @@ const useChatWidgetState = () =>
|
||||
});
|
||||
|
||||
if (!avatarImage) {
|
||||
resolve(null);
|
||||
return;
|
||||
}
|
||||
|
||||
avatarImage.getCroppedImage(AvatarSetType.HEAD).then(image => {
|
||||
|
||||
if (!image || !image.src) {
|
||||
avatarImage.dispose();
|
||||
resolve(null);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -80,43 +81,57 @@ const useChatWidgetState = () =>
|
||||
resolve(image.src);
|
||||
}).catch(error => {
|
||||
avatarImage.dispose();
|
||||
resolve(null);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const getUserImage = (figure: string, username: string): string | null => {
|
||||
const getUserImage = async (figure: string, username: string): Promise<string | null> => {
|
||||
let existing = avatarImageCache.get(figure);
|
||||
|
||||
if (!existing) {
|
||||
setFigureImage(figure, username).then(src => {
|
||||
const src = await setFigureImage(figure, username);
|
||||
if (src) {
|
||||
avatarImageCache.set(figure, src);
|
||||
});
|
||||
return;
|
||||
return src;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
return existing;
|
||||
};
|
||||
|
||||
const getPetImage = (figure: string, direction: number, _arg_3: boolean, scale: number = 64, posture: string = null) =>
|
||||
{
|
||||
let existing = petImageCache.get((figure + posture));
|
||||
const getPetImage = async (figure: string, direction: number, _arg_3: boolean, scale: number = 64, posture: string = null): Promise<string | null> => {
|
||||
const cacheKey = (figure + posture);
|
||||
let existing = petImageCache.get(cacheKey);
|
||||
|
||||
if(existing) return existing;
|
||||
if (existing) return existing;
|
||||
|
||||
const figureData = new PetFigureData(figure);
|
||||
const typeId = figureData.typeId;
|
||||
const image = GetRoomEngine().getRoomObjectPetImage(typeId, figureData.paletteId, figureData.color, new Vector3d((direction * 45)), scale, null, false, 0, figureData.customParts, posture);
|
||||
|
||||
if(image)
|
||||
{
|
||||
existing = TextureUtils.generateImageUrl(image.data);
|
||||
petImageCache.set((figure + posture), existing);
|
||||
let image = GetRoomEngine().getRoomObjectPetImage(typeId, figureData.paletteId, figureData.color, new Vector3d((direction * 45)), scale, null, false, 0, figureData.customParts, posture);
|
||||
|
||||
if (!image) {
|
||||
image = GetRoomEngine().getRoomObjectPetImage(typeId, figureData.paletteId, figureData.color, new Vector3d(90), 64, null, false, 0, figureData.customParts, null);
|
||||
}
|
||||
|
||||
return existing;
|
||||
if (image) {
|
||||
const imageUrl = await TextureUtils.generateImageUrl(image.data);
|
||||
if (imageUrl) {
|
||||
petImageCache.set(cacheKey, imageUrl);
|
||||
return imageUrl;
|
||||
} else {
|
||||
petImageCache.delete(cacheKey);
|
||||
}
|
||||
} else {
|
||||
petImageCache.delete(cacheKey);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
useRoomSessionManagerEvent<RoomSessionChatEvent>(RoomSessionChatEvent.CHAT_EVENT, event =>
|
||||
useRoomSessionManagerEvent<RoomSessionChatEvent>(RoomSessionChatEvent.CHAT_EVENT, async event =>
|
||||
{
|
||||
const roomObject = GetRoomEngine().getRoomObject(roomSession.roomId, event.objectId, RoomObjectCategory.UNIT);
|
||||
const bubbleLocation = roomObject ? GetRoomObjectScreenLocation(roomSession.roomId, roomObject?.id, RoomObjectCategory.UNIT) : new NitroPoint();
|
||||
@ -135,18 +150,17 @@ const useChatWidgetState = () =>
|
||||
if(userData)
|
||||
{
|
||||
userType = userData.type;
|
||||
|
||||
const figure = userData.figure;
|
||||
|
||||
switch(userType)
|
||||
{
|
||||
case RoomObjectType.PET:
|
||||
imageUrl = getPetImage(figure, 2, true, 64, roomObject.model.getValue<string>(RoomObjectVariable.FIGURE_POSTURE));
|
||||
imageUrl = await getPetImage(figure, 2, true, 64, roomObject.model.getValue<string>(RoomObjectVariable.FIGURE_POSTURE));
|
||||
petType = new PetFigureData(figure).typeId;
|
||||
chatColours = "black";
|
||||
break;
|
||||
case RoomObjectType.USER:
|
||||
imageUrl = getUserImage(figure, userData.name);
|
||||
imageUrl = await getUserImage(figure, userData.name);
|
||||
break;
|
||||
case RoomObjectType.RENTABLE_BOT:
|
||||
case RoomObjectType.BOT:
|
||||
@ -163,36 +177,27 @@ const useChatWidgetState = () =>
|
||||
{
|
||||
case RoomSessionChatEvent.CHAT_TYPE_RESPECT:
|
||||
text = LocalizeText('widgets.chatbubble.respect', [ 'username' ], [ username ]);
|
||||
|
||||
if(GetConfiguration('respect.options')['enabled']) PlaySound(GetConfiguration('respect.options')['sound']);
|
||||
|
||||
break;
|
||||
case RoomSessionChatEvent.CHAT_TYPE_PETREVIVE:
|
||||
case RoomSessionChatEvent.CHAT_TYPE_PET_REBREED_FERTILIZE:
|
||||
case RoomSessionChatEvent.CHAT_TYPE_PET_SPEED_FERTILIZE: {
|
||||
let textKey = 'widget.chatbubble.petrevived';
|
||||
|
||||
if(chatType === RoomSessionChatEvent.CHAT_TYPE_PET_REBREED_FERTILIZE)
|
||||
{
|
||||
textKey = 'widget.chatbubble.petrefertilized;';
|
||||
}
|
||||
|
||||
else if(chatType === RoomSessionChatEvent.CHAT_TYPE_PET_SPEED_FERTILIZE)
|
||||
{
|
||||
textKey = 'widget.chatbubble.petspeedfertilized';
|
||||
}
|
||||
|
||||
let targetUserName: string = null;
|
||||
|
||||
const newRoomObject = GetRoomEngine().getRoomObject(roomSession.roomId, event.extraParam, RoomObjectCategory.UNIT);
|
||||
|
||||
if(newRoomObject)
|
||||
{
|
||||
const newUserData = roomSession.userDataManager.getUserDataByIndex(roomObject.id);
|
||||
|
||||
if(newUserData) targetUserName = newUserData.name;
|
||||
}
|
||||
|
||||
text = LocalizeText(textKey, [ 'petName', 'userName' ], [ username, targetUserName ]);
|
||||
break;
|
||||
}
|
||||
@ -209,7 +214,6 @@ const useChatWidgetState = () =>
|
||||
const hours = ((event.extraParam > 0) ? Math.floor((event.extraParam / 3600)) : 0).toString();
|
||||
const minutes = ((event.extraParam > 0) ? Math.floor((event.extraParam % 3600) / 60) : 0).toString();
|
||||
const seconds = (event.extraParam % 60).toString();
|
||||
|
||||
text = LocalizeText('widget.chatbubble.mutetime', [ 'hours', 'minutes', 'seconds' ], [ hours, minutes, seconds ]);
|
||||
break;
|
||||
}
|
||||
@ -233,6 +237,9 @@ const useChatWidgetState = () =>
|
||||
chatColours
|
||||
);
|
||||
|
||||
// Add userType to ChatBubbleMessage as a dynamic property
|
||||
(chatMessage as any).userType = userType;
|
||||
|
||||
setChatMessages(prevValue => [ ...prevValue, chatMessage ]);
|
||||
addChatEntry({ id: -1, webId: userData.webID, entityId: userData.roomIndex, name: username, imageUrl, style: styleId, chatType: chatType, entityType: userData.type, message: formattedText, timestamp: ChatHistoryCurrentDate(), type: ChatEntryType.TYPE_CHAT, roomId: roomSession.roomId, color, chatColours });
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user