diff --git a/src/api/room/widgets/ChatBubbleMessage.ts b/src/api/room/widgets/ChatBubbleMessage.ts index 44d848c..b97a5a6 100644 --- a/src/api/room/widgets/ChatBubbleMessage.ts +++ b/src/api/room/widgets/ChatBubbleMessage.ts @@ -22,10 +22,13 @@ export class ChatBubbleMessage public type: number = 0, public styleId: number = 0, public imageUrl: string = null, - public color: string = null + public color: string = null, + public chatColours: string = "" ) { this.id = ++ChatBubbleMessage.BUBBLE_COUNTER; + this.color = color; + this.chatColours = chatColours; } public get top(): number diff --git a/src/api/utils/RoomChatFormatter.ts b/src/api/utils/RoomChatFormatter.ts index a24cdf5..d56044a 100644 --- a/src/api/utils/RoomChatFormatter.ts +++ b/src/api/utils/RoomChatFormatter.ts @@ -1,4 +1,5 @@ -const allowedColours: Map = new Map(); +import { GetConfiguration } from '@nitrots/nitro-renderer'; +export const allowedColours: Map = new Map(); allowedColours.set('r', 'red'); allowedColours.set('b', 'blue'); @@ -11,16 +12,109 @@ allowedColours.set('br', 'brown'); allowedColours.set('pr', 'purple'); allowedColours.set('pk', 'pink'); +allowedColours.set('black', 'black'); allowedColours.set('red', 'red'); -allowedColours.set('blue', 'blue'); -allowedColours.set('green', 'green'); -allowedColours.set('yellow', 'yellow'); -allowedColours.set('white', 'white'); +allowedColours.set('orangered', 'orangered'); allowedColours.set('orange', 'orange'); -allowedColours.set('cyan', 'cyan'); -allowedColours.set('brown', 'brown'); +allowedColours.set('yellow', 'yellow'); +allowedColours.set('yellowgreen', 'yellowgreen'); +allowedColours.set('green', 'green'); +allowedColours.set('seagreen', 'seagreen'); +allowedColours.set('teal', 'teal'); +allowedColours.set('blue', 'blue'); +allowedColours.set('darkblue', 'darkblue'); +allowedColours.set('indigo', 'indigo'); allowedColours.set('purple', 'purple'); -allowedColours.set('pink', 'pink'); +allowedColours.set('violet', 'violet'); +allowedColours.set('brown', 'brown'); +allowedColours.set('burlywood', 'burlywood'); +allowedColours.set('rosybrown', 'rosybrown'); +allowedColours.set('saddlebrown', 'saddlebrown'); +allowedColours.set('maroon', 'maroon'); +allowedColours.set('firebrick', 'firebrick'); +allowedColours.set('darkred', 'darkred'); +allowedColours.set('chocolate', 'chocolate'); +allowedColours.set('sienna', 'sienna'); +allowedColours.set('peru', 'peru'); +allowedColours.set('darkorange', 'darkorange'); +allowedColours.set('orange', 'orange'); +allowedColours.set('orangered', 'orangered'); +allowedColours.set('tomato', 'tomato'); +allowedColours.set('coral', 'coral'); +allowedColours.set('darkolivegreen', 'darkolivegreen'); +allowedColours.set('olive', 'olive'); +allowedColours.set('olivedrab', 'olivedrab'); +allowedColours.set('greenyellow', 'greenyellow'); +allowedColours.set('yellowgreen', 'yellowgreen'); +allowedColours.set('darkgreen', 'darkgreen'); +allowedColours.set('limegreen', 'limegreen'); +allowedColours.set('lime', 'lime'); +allowedColours.set('lawngreen', 'lawngreen'); +allowedColours.set('palegreen', 'palegreen'); +allowedColours.set('springgreen', 'springgreen'); +allowedColours.set('mediumseagreen', 'mediumseagreen'); +allowedColours.set('mediumaquamarine', 'mediumaquamarine'); +allowedColours.set('aquamarine', 'aquamarine'); +allowedColours.set('turquoise', 'turquoise'); +allowedColours.set('mediumturquoise', 'mediumturquoise'); +allowedColours.set('darkturquoise', 'darkturquoise'); +allowedColours.set('aqua', 'aqua'); +allowedColours.set('cyan', 'cyan'); +allowedColours.set('lightcyan', 'lightcyan'); +allowedColours.set('paleturquoise', 'paleturquoise'); +allowedColours.set('azure', 'azure'); +allowedColours.set('lightblue', 'lightblue'); +allowedColours.set('powderblue', 'powderblue'); +allowedColours.set('deepskyblue', 'deepskyblue'); +allowedColours.set('skyblue', 'skyblue'); +allowedColours.set('lightskyblue', 'lightskyblue'); +allowedColours.set('steelblue', 'steelblue'); +allowedColours.set('royalblue', 'royalblue'); +allowedColours.set('mediumslateblue', 'mediumslateblue'); +allowedColours.set('slateblue', 'slateblue'); +allowedColours.set('darkslateblue', 'darkslateblue'); +allowedColours.set('mediumpurple', 'mediumpurple'); +allowedColours.set('blueviolet', 'blueviolet'); +allowedColours.set('darkviolet', 'darkviolet'); +allowedColours.set('darkmagenta', 'darkmagenta'); +allowedColours.set('mediumvioletred', 'mediumvioletred'); +allowedColours.set('violetred', 'violetred'); +allowedColours.set('orchid', 'orchid'); +allowedColours.set('darkorchid', 'darkorchid'); +allowedColours.set('mediumorchid', 'mediumorchid'); +allowedColours.set('thistle', 'thistle'); +allowedColours.set('plum', 'plum'); +allowedColours.set('purple', 'purple'); +allowedColours.set('darkgrey', 'darkgrey'); +allowedColours.set('dimgray', 'dimgray'); +allowedColours.set('lightgrey', 'lightgrey'); +allowedColours.set('grey', 'grey'); +allowedColours.set('slategrey', 'slategrey'); +allowedColours.set('lightslategrey', 'lightslategrey'); +allowedColours.set('whitesmoke', 'whitesmoke'); +allowedColours.set('white', 'white'); +allowedColours.set('snow', 'snow'); +allowedColours.set('mistyrose', 'mistyrose'); +allowedColours.set('seashell', 'seashell'); +allowedColours.set('antiquewhite', 'antiquewhite'); +allowedColours.set('linen', 'linen'); +allowedColours.set('oldlace', 'oldlace'); +allowedColours.set('papayawhip', 'papayawhip'); +allowedColours.set('blanchedalmond', 'blanchedalmond'); +allowedColours.set('moccasin', 'moccasin'); +allowedColours.set('wheat', 'wheat'); +allowedColours.set('navajowhite', 'navajowhite'); +allowedColours.set('burlywood', 'burlywood'); +allowedColours.set('tan', 'tan'); +allowedColours.set('rosybrown', 'rosybrown'); +allowedColours.set('sandybrown', 'sandybrown'); +allowedColours.set('goldenrod', 'goldenrod'); +allowedColours.set('darkgoldenrod', 'darkgoldenrod'); +allowedColours.set('peru', 'peru'); +allowedColours.set('chocolate', 'chocolate'); +allowedColours.set('saddlebrown', 'saddlebrown'); +allowedColours.set('sienna', 'sienna'); +allowedColours.set('brown', 'brown'); const encodeHTML = (str: string) => { @@ -37,37 +131,38 @@ const encodeHTML = (str: string) => }); } -export const RoomChatFormatter = (content: string) => -{ +export const RoomChatFormatter = (content: string) => { let result = ''; content = encodeHTML(content); - //content = (joypixels.shortnameToUnicode(content) as string) + content = content.replace(/\[tag\](.*?)\[\/tag\]/g, '$1'); - if(content.startsWith('@') && content.indexOf('@', 1) > -1) - { - let match = null; - - while((match = /@[a-zA-Z]+@/g.exec(content)) !== null) - { - const colorTag = match[0].toString(); - const colorName = colorTag.substr(1, colorTag.length - 2); - const text = content.replace(colorTag, ''); - - if(!allowedColours.has(colorName)) - { - result = text; - } - else - { - const color = allowedColours.get(colorName); - result = '' + text + ''; - } - break; - } + // Youtube link + if (!GetConfiguration('youtube.publish.disabled', false)) { + content = content.replace( + /(?:http:\/\/|https:\/\/)?(?:www\.)?(?:youtube\.com|youtu\.be)\/(?:watch\?.*v=|shorts\/)?([a-zA-Z0-9_-]{11})/g, + ` +
+ YouTube IconClick on open video to see the YouTube video +
Open Video
+ ` + ); } - else - { + + const match = content.match(/@[a-zA-Z]+@/); + if (match) { + const colorTag = match[0].toString(); + const colorName = colorTag.substr(1, colorTag.length - 2); + const text = content.replace(colorTag, ''); + + if (!allowedColours.has(colorName)) { + result = text; + } else { + const color = allowedColours.get(colorName); + result = `${text}`; + } + } else { result = content; } diff --git a/src/components/room/widgets/chat-input/ChatInputColorSelectorView.tsx b/src/components/room/widgets/chat-input/ChatInputColorSelectorView.tsx new file mode 100644 index 0000000..e407d9f --- /dev/null +++ b/src/components/room/widgets/chat-input/ChatInputColorSelectorView.tsx @@ -0,0 +1,66 @@ +import { FC, useEffect, useRef, useState } from 'react'; +import { Overlay, Popover } from 'react-bootstrap'; +import { MdFormatColorText } from 'react-icons/md'; +import { allowedColours } from '../../../../api'; +import { AutoGrid, Base, LayoutGridItem, NitroCardContentView } from '../../../../common'; + +interface ChatInputColorSelectorViewProps +{ + chatColour: string; + selectColour: (color: string) => void; +} + +export const ChatInputColorSelectorView: FC = props => +{ + const { chatColour = 'black', selectColour = null } = props; + const [ selectorVisible, setSelectorVisible ] = useState(false); + const [ colours, setColours ] = useState>(null); + const iconRef = useRef(null); + + useEffect(() => + { + const excludedColors = new Set(["r", "b", "g", "y", "w", "o", "c", "br", "pr", "pk"]); + const uniqueColours = new Map(); + + allowedColours.forEach((value, key) => { + if (!excludedColors.has(key) && !Array.from(uniqueColours.values()).includes(value)) { + uniqueColours.set(key, value); + } + }); + + setColours(uniqueColours); + }, []); + + const selectColor = (color: string) => + { + selectColour(color); + setSelectorVisible(false); + } + + const toggleSelector = () => + { + setSelectorVisible(prevValue => !prevValue); + } + + return ( + <> + toggleSelector() } innerRef={ iconRef } style={ { color: (colours && colours.get(chatColour)) ?? 'black' } }> + + + + + + + { colours && (colours.size > 0) && Array.from(colours).map(([ color, hex ]) => + { + return ( + selectColor(color) } key={ color }> + ); + }) } + + + + + + ); +} diff --git a/src/components/room/widgets/chat-input/ChatInputEmojiSelectorView.tsx b/src/components/room/widgets/chat-input/ChatInputEmojiSelectorView.tsx new file mode 100644 index 0000000..57d6212 --- /dev/null +++ b/src/components/room/widgets/chat-input/ChatInputEmojiSelectorView.tsx @@ -0,0 +1,69 @@ +import { FC, MouseEvent, useEffect, useRef, useState } from 'react'; +import { Overlay, Popover } from 'react-bootstrap'; +import { Base, Flex, Grid, NitroCardContentView } from '../../../../common'; +import data from '@emoji-mart/data' +import Picker from '@emoji-mart/react' + +interface ChatInputEmojiSelectorViewProps +{ + addChatEmoji: (emoji: string) => void; +} + +export const ChatInputEmojiSelectorView: FC = props => +{ + const { addChatEmoji = null } = props; + const [ selectorVisible, setSelectorVisible ] = useState(false); + const [ target, setTarget ] = useState<(EventTarget & HTMLElement)>(null); + const iconRef = useRef(null); + const componentRef = useRef(null); + + const handleEmojiSelect = (emoji: any) => { + addChatEmoji(emoji.native); + setSelectorVisible(false); + } + + const addEmojiToChat = (emoji: string) => { + setChatValue(chatValue + emoji); + setIsTyping(true); + }; + + const handleClickOutside = (event: MouseEvent) => { + const className = 'emoji-icon'; + if (componentRef.current && !componentRef.current.contains(event.target as Node) && !(event.target as Element).classList.contains(className)) { + setSelectorVisible(false); + document.removeEventListener('mousedown', handleClickOutside as any); + setTarget(null); + } + }; + + const selectEmoji = (emoji: string) => + { + addChatEmoji(emoji); + } + + const toggleSelector = (event: MouseEvent) => + { + setSelectorVisible(prevValue => !prevValue); + } + + useEffect(() => + { + if(selectorVisible) { + document.addEventListener('mousedown', handleClickOutside as any); + } + else { + setTarget(null); + } + }, [ componentRef, selectorVisible ]); + + return ( + <> + 🙂 + + + + + + + ); +} \ No newline at end of file diff --git a/src/components/room/widgets/chat-input/ChatInputStyleSelectorView.tsx b/src/components/room/widgets/chat-input/ChatInputStyleSelectorView.tsx index f5cbbe5..640e985 100644 --- a/src/components/room/widgets/chat-input/ChatInputStyleSelectorView.tsx +++ b/src/components/room/widgets/chat-input/ChatInputStyleSelectorView.tsx @@ -46,9 +46,9 @@ export const ChatInputStyleSelectorView: FC = p <> - - - + + + { chatStyleIds && (chatStyleIds.length > 0) && chatStyleIds.map((styleId) => { return ( diff --git a/src/components/room/widgets/chat-input/ChatInputView.scss b/src/components/room/widgets/chat-input/ChatInputView.scss index ca1357a..8a410ca 100644 --- a/src/components/room/widgets/chat-input/ChatInputView.scss +++ b/src/components/room/widgets/chat-input/ChatInputView.scss @@ -1,29 +1,30 @@ .nitro-chat-input-container { display: flex; - justify-content: center; + justify-content: space-between; align-items: center; position: relative; height: 40px; - border-radius: 8px; - border: 2px solid rgb(0, 0, 0); - background: #EDEDED; + border-radius: 0.5rem; + border: 2px solid #000; + box-shadow: 0 0 0 1pt #fff!important; + background: #ededed; padding-right: 10px; - width: 100%; overflow: hidden; + width: -webkit-fill-available; - @include media-breakpoint-down(sm) { - display: flex; + @include media-breakpoint-down(xxl) { position: absolute; bottom: 70px; - left: calc(100% / 3); - width: 200px; + left: 50%; + transform: translateX(-50%); + width: auto; } &:before { content: ''; position: absolute; width: 100%; - height: 5px; + height: 2px; top: 1px; left: 0; right: 0; @@ -51,18 +52,45 @@ border: none; outline: none; } - - &::after { + + &::after { content: attr(data-value) ' '; + visibility: hidden; white-space: pre-wrap; - } + } + } + + .colour-container { + visibility: visible; + width: 100%; } .bubble-container { visibility: visible; width: 75%; } + .nitro-chat-style-box { + height: 100%; + z-index: 1; + display: flex; + align-items: center; + padding: 0 0.75em; + margin-right: 2px; + width: 34px; + } +} + +.nitro-chat-input-container .input-sizer input{ + width: auto; + min-width: 1em; + grid-area: 1 / 2; + margin: 0; + resize: none; + background: none; + appearance: none; + border: none; + outline: none; } .nitro-chat-style-selector-container { @@ -73,6 +101,34 @@ max-height: $chat-input-style-selector-widget-height !important; } + .emoji { + position: relative; + z-index: 1; + transition: transform 0.3s; + } + + .emoji::before { + content: ""; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: lightblue; + border-radius: 60%; + z-index: -1; + opacity: 0; + transition: opacity 0.3s; + } + + .emoji:hover::before { + opacity: 1; + } + + .emoji:hover { + transform: scale(1.5); + } + .bubble-parent-container { height: 30px; @@ -82,3 +138,20 @@ } } } + +.info-habbopages { + cursor: pointer; + background-image: url("@/assets/images/boxes/card/questionmark.png"); + width: 19px; + height: 20px; + right: -2px; + position: relative; + + &:hover { + background-image: url("@/assets/images/boxes/card/questionmark_hover.png"); + + &:active { + background-image: url("@/assets/images/boxes/card/questionmark_click.png"); + } + } +} diff --git a/src/components/room/widgets/chat-input/ChatInputView.tsx b/src/components/room/widgets/chat-input/ChatInputView.tsx index 8f66b2d..615dcba 100644 --- a/src/components/room/widgets/chat-input/ChatInputView.tsx +++ b/src/components/room/widgets/chat-input/ChatInputView.tsx @@ -1,19 +1,23 @@ -import { GetSessionDataManager, HabboClubLevelEnum, RoomControllerLevel } from '@nitrots/nitro-renderer'; +import { CreateLinkEvent, GetSessionDataManager, HabboClubLevelEnum, RoomControllerLevel } from '@nitrots/nitro-renderer'; import { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { createPortal } from 'react-dom'; -import { ChatMessageTypeEnum, GetClubMemberLevel, GetConfigurationValue, LocalizeText, RoomWidgetUpdateChatInputContentEvent } from '../../../../api'; -import { Text } from '../../../../common'; +import { ChatMessageTypeEnum, GetConfigurationValue, GetClubMemberLevel, LocalizeText, RoomWidgetUpdateChatInputContentEvent } from '../../../../api'; +import { Base, Flex, Text } from '../../../../common'; import { useChatInputWidget, useRoom, useSessionInfo, useUiEvent } from '../../../../hooks'; import { ChatInputStyleSelectorView } from './ChatInputStyleSelectorView'; +import { ChatInputEmojiSelectorView } from './ChatInputEmojiSelectorView'; +import { ChatInputColorSelectorView } from './ChatInputColorSelectorView'; + export const ChatInputView: FC<{}> = props => { const [ chatValue, setChatValue ] = useState(''); - const { chatStyleId = 0, updateChatStyleId = null } = useSessionInfo(); + const { chatStyleId = 0, updateChatStyleId = null, chatColour = '', updateChatColour = null } = useSessionInfo(); const { selectedUsername = '', floodBlocked = false, floodBlockedSeconds = 0, setIsTyping = null, setIsIdle = null, sendChat = null } = useChatInputWidget(); + const [ showInfoHabboPages, setShowInfohabboPages ] = useState(false); const { roomSession = null } = useRoom(); - const inputRef = useRef(); + const inputRef = useRef(); const chatModeIdWhisper = useMemo(() => LocalizeText('widgets.chatinput.mode.whisper'), []); const chatModeIdShout = useMemo(() => LocalizeText('widgets.chatinput.mode.shout'), []); const chatModeIdSpeak = useMemo(() => LocalizeText('widgets.chatinput.mode.speak'), []); @@ -97,12 +101,12 @@ export const ChatInputView: FC<{}> = props => else { setChatValue(''); - sendChat(text, chatType, recipientName, chatStyleId); + sendChat(text, chatType, recipientName, chatStyleId, chatColour); } } setChatValue(append); - }, [ chatModeIdWhisper, chatModeIdShout, chatModeIdSpeak, maxChatLength, chatStyleId, setIsTyping, setIsIdle, sendChat ]); + }, [ chatModeIdWhisper, chatModeIdShout, chatModeIdSpeak, maxChatLength, chatStyleId, setIsTyping, setIsIdle, sendChat, chatColour ]); const updateChatInput = useCallback((value: string) => { @@ -214,6 +218,11 @@ export const ChatInputView: FC<{}> = props => return styleIds; }, []); + const addEmojiToChat = (emoji: string) => { + setChatValue(chatValue + emoji); + setIsTyping(true); + }; + useEffect(() => { document.body.addEventListener('keydown', onKeyDownEvent); @@ -232,17 +241,33 @@ export const ChatInputView: FC<{}> = props => }, [ chatValue ]); if(!roomSession || roomSession.isSpectator) return null; - + return ( createPortal( -
-
+
setShowInfohabboPages(true) } onMouseLeave={ () => setTimeout(() => setShowInfohabboPages(false), 100) }> +
{ !floodBlocked && - updateChatInput(event.target.value) } onMouseDown={ event => setInputFocus() } /> } + updateChatInput(event.target.value) } + onMouseDown={ event => setInputFocus() } /> + } { floodBlocked && { LocalizeText('chat.input.alert.flood', [ 'time' ], [ floodBlockedSeconds.toString() ]) } }
- -
, document.getElementById('toolbar-chat-input-container')) + + + + + { + (showInfoHabboPages) && CreateLinkEvent('habbopages/chat/chatting') }> + } + +
, document.getElementById('toolbar-chat-input-container')) ); -} +} \ No newline at end of file diff --git a/src/components/room/widgets/chat/ChatWidgetMessageView.tsx b/src/components/room/widgets/chat/ChatWidgetMessageView.tsx index cda6c91..a44f9cb 100644 --- a/src/components/room/widgets/chat/ChatWidgetMessageView.tsx +++ b/src/components/room/widgets/chat/ChatWidgetMessageView.tsx @@ -7,11 +7,11 @@ interface ChatWidgetMessageViewProps chat: ChatBubbleMessage; makeRoom: (chat: ChatBubbleMessage) => void; bubbleWidth?: number; + selectedEmoji?: string; } -export const ChatWidgetMessageView: FC = props => -{ - const { chat = null, makeRoom = null, bubbleWidth = RoomChatSettings.CHAT_BUBBLE_WIDTH_NORMAL } = props; +export const ChatWidgetMessageView: FC = props => { + const { chat = null, makeRoom = null, bubbleWidth = RoomChatSettings.CHAT_BUBBLE_WIDTH_NORMAL, selectedEmoji } = props; const [ isVisible, setIsVisible ] = useState(false); const [ isReady, setIsReady ] = useState(false); const elementRef = useRef(); @@ -76,8 +76,9 @@ export const ChatWidgetMessageView: FC = props => }, [ chat, isReady, isVisible, makeRoom ]); return ( -
GetRoomEngine().selectRoomObject(chat.roomId, chat.senderId, RoomObjectCategory.UNIT) }> - { (chat.styleId === 0) && +
GetRoomEngine().selectRoomObject(chat.roomId, chat.senderId, RoomObjectCategory.UNIT)}> + {selectedEmoji && {DOMPurify.sanitize(selectedEmoji)}} + { (chat.styleId === 0) &&
}
@@ -86,10 +87,10 @@ export const ChatWidgetMessageView: FC = props =>
- + onClickChat(e)} />
); -} +} \ No newline at end of file diff --git a/src/components/toolbar/ToolbarView.tsx b/src/components/toolbar/ToolbarView.tsx index 28bc2b1..a5758a5 100644 --- a/src/components/toolbar/ToolbarView.tsx +++ b/src/components/toolbar/ToolbarView.tsx @@ -1,6 +1,6 @@ -import { CreateLinkEvent, Dispose, DropBounce, EaseOut, GetConfiguration, GetSessionDataManager, JumpBy, Motions, NitroToolbarAnimateIconEvent, PerkAllowancesMessageEvent, PerkEnum, Queue, Wait } from '@nitrots/nitro-renderer'; +import { CreateLinkEvent, Dispose, DropBounce, EaseOut, GetSessionDataManager, JumpBy, Motions, NitroToolbarAnimateIconEvent, PerkAllowancesMessageEvent, PerkEnum, Queue, Wait } from '@nitrots/nitro-renderer'; import { FC, useState } from 'react'; -import { MessengerIconState, OpenMessengerChat, VisitDesktop } from '../../api'; +import { GetConfigurationValue, MessengerIconState, OpenMessengerChat, VisitDesktop } from '../../api'; import { Base, Flex, LayoutAvatarImageView, LayoutItemCountView, TransitionAnimation, TransitionAnimationTypes } from '../../common'; import { useAchievements, useFriends, useInventoryUnseenTracker, useMessageEvent, useMessenger, useNitroEvent, useSessionInfo } from '../../hooks'; import { ToolbarMeView } from './ToolbarMeView'; @@ -81,7 +81,7 @@ export const ToolbarView: FC<{ isInRoom: boolean }> = props => { !isInRoom && CreateLinkEvent('navigator/goto/home') } /> } CreateLinkEvent('navigator/toggle') } /> - { GetConfiguration('game.center.enabled') && CreateLinkEvent('games/toggle') } /> } + { GetConfigurationValue('game.center.enabled') && CreateLinkEvent('games/toggle') } /> } CreateLinkEvent('catalog/toggle') } /> CreateLinkEvent('inventory/toggle') }> { (getFullCount > 0) && diff --git a/src/hooks/chat-history/useChatHistory.ts b/src/hooks/chat-history/useChatHistory.ts index b8545a0..c8b6fa7 100644 --- a/src/hooks/chat-history/useChatHistory.ts +++ b/src/hooks/chat-history/useChatHistory.ts @@ -3,6 +3,7 @@ import { useState } from 'react'; import { useBetween } from 'use-between'; import { ChatEntryType, ChatHistoryCurrentDate, IChatEntry, IRoomHistoryEntry, MessengerHistoryCurrentDate } from '../../api'; import { useMessageEvent, useNitroEvent } from '../events'; +import { useLocalStorage } from '../useLocalStorage'; const CHAT_HISTORY_MAX = 1000; const ROOM_HISTORY_MAX = 10; @@ -13,10 +14,10 @@ let MESSENGER_HISTORY_COUNTER: number = 0; const useChatHistoryState = () => { - const [ chatHistory, setChatHistory ] = useState([]); - const [ roomHistory, setRoomHistory ] = useState([]); - const [ messengerHistory, setMessengerHistory ] = useState([]); - const [ needsRoomInsert, setNeedsRoomInsert ] = useState(false); + const [ chatHistory, setChatHistory ] = useLocalStorage('chatHistory', []); + const [ roomHistory, setRoomHistory ] = useLocalStorage('roomHistory', []); + const [ messengerHistory, setMessengerHistory ] = useLocalStorage('messengerHistory', []); + const [ needsRoomInsert, setNeedsRoomInsert ] = useLocalStorage('needsRoomInsert', false); const addChatEntry = (entry: IChatEntry) => { diff --git a/src/hooks/events/useRoomSessionManagerEvent.tsx b/src/hooks/events/useRoomSessionManagerEvent.tsx new file mode 100644 index 0000000..3d87162 --- /dev/null +++ b/src/hooks/events/useRoomSessionManagerEvent.tsx @@ -0,0 +1,4 @@ +import { GetRoomSessionManager, NitroEvent } from '@nitrots/nitro-renderer'; +import { useEventDispatcher } from './useEventDispatcher'; + +export const useRoomSessionManagerEvent = (type: string | string[], handler: (event: T) => void) => useEventDispatcher(type, GetRoomSessionManager().events, handler); diff --git a/src/hooks/rooms/widgets/useChatInputWidget.ts b/src/hooks/rooms/widgets/useChatInputWidget.ts index db47e02..7efedbd 100644 --- a/src/hooks/rooms/widgets/useChatInputWidget.ts +++ b/src/hooks/rooms/widgets/useChatInputWidget.ts @@ -17,7 +17,7 @@ const useChatInputWidgetState = () => const { showNitroAlert = null, showConfirm = null } = useNotification(); const { roomSession = null } = useRoom(); - const sendChat = (text: string, chatType: number, recipientName: string = '', styleId: number = 0) => + const sendChat = (text: string, chatType: number, recipientName: string = '', styleId: number = 0, chatColour: string = '') => { if(text === '') return null; @@ -108,8 +108,17 @@ const useChatInputWidgetState = () => return null; case ':zoom': - GetEventDispatcher().dispatchEvent(new RoomZoomEvent(roomSession.roomId, parseFloat(secondPart), false)); - + let requestedZoomLevel = parseFloat(secondPart); + if (isNaN(requestedZoomLevel)) { + requestedZoomLevel = 1; + } + if (requestedZoomLevel >= 1 && requestedZoomLevel <= 5) { + GetRoomEngine().events.dispatchEvent(new RoomZoomEvent(roomSession.roomId, requestedZoomLevel, false)); + } else if (requestedZoomLevel === 0) { + GetRoomEngine().events.dispatchEvent(new RoomZoomEvent(roomSession.roomId, 1, false)); + } else { + GetRoomEngine().events.dispatchEvent(new RoomZoomEvent(roomSession.roomId, 1, false)); + }; return null; case ':screenshot': const texture = GetRoomEngine().createTextureFromRoom(roomSession.roomId, 1); @@ -131,7 +140,7 @@ const useChatInputWidgetState = () => { GetSessionDataManager().sendSpecialCommandMessage(':pickall'); }, - null, null, null, LocalizeText('generic.alert.title')); + null, null, null, LocalizeText('generic.alert.title'), null, 'pickall'); } return null; @@ -180,10 +189,10 @@ const useChatInputWidgetState = () => switch(chatType) { case ChatMessageTypeEnum.CHAT_DEFAULT: - roomSession.sendChatMessage(text, styleId); + roomSession.sendChatMessage(text, styleId, chatColour); break; case ChatMessageTypeEnum.CHAT_SHOUT: - roomSession.sendShoutMessage(text, styleId); + roomSession.sendChatMessage(text, styleId, chatColour); break; case ChatMessageTypeEnum.CHAT_WHISPER: roomSession.sendWhisperMessage(recipientName, text, styleId); @@ -216,7 +225,7 @@ const useChatInputWidgetState = () => let seconds = 0; - const interval = setInterval(() => + const interval = window.setInterval(() => { setFloodBlockedSeconds(prevValue => { diff --git a/src/hooks/rooms/widgets/useChatWidget.ts b/src/hooks/rooms/widgets/useChatWidget.ts index a723a51..b31ae1f 100644 --- a/src/hooks/rooms/widgets/useChatWidget.ts +++ b/src/hooks/rooms/widgets/useChatWidget.ts @@ -48,6 +48,7 @@ const useChatWidgetState = () => let userType = 0; let petType = -1; let text = event.message; + let chatColours = event._chatColours if(userData) { @@ -60,6 +61,7 @@ const useChatWidgetState = () => case RoomObjectType.PET: imageUrl = await ChatBubbleUtilities.getPetImage(figure, 2, true, 64, roomObject.model.getValue(RoomObjectVariable.FIGURE_POSTURE)); petType = new PetFigureData(figure).typeId; + chatColours = "black" break; case RoomObjectType.USER: imageUrl = await ChatBubbleUtilities.getUserImage(figure); @@ -67,6 +69,7 @@ const useChatWidgetState = () => case RoomObjectType.RENTABLE_BOT: case RoomObjectType.BOT: styleId = SystemChatStyleEnum.BOT; + chatColours = "black" break; } @@ -144,10 +147,11 @@ const useChatWidgetState = () => chatType, styleId, imageUrl, - color); + color, + chatColours); 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 }); + 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 }); }); useNitroEvent(RoomDragEvent.ROOM_DRAG, event => @@ -188,4 +192,4 @@ const useChatWidgetState = () => return { chatMessages, setChatMessages, chatSettings, getScrollSpeed }; } -export const useChatWidget = useChatWidgetState; +export const useChatWidget = useChatWidgetState; \ No newline at end of file diff --git a/src/hooks/session/useSessionInfo.ts b/src/hooks/session/useSessionInfo.ts index 122991d..73fe9d0 100644 --- a/src/hooks/session/useSessionInfo.ts +++ b/src/hooks/session/useSessionInfo.ts @@ -1,14 +1,16 @@ import { FigureUpdateEvent, GetSessionDataManager, RoomUnitChatStyleComposer, UserInfoDataParser, UserInfoEvent, UserSettingsEvent } from '@nitrots/nitro-renderer'; import { useState } from 'react'; import { useBetween } from 'use-between'; -import { SendMessageComposer } from '../../api'; +import { GetLocalStorage, SendMessageComposer } from '../../api'; import { useMessageEvent } from '../events'; +import { useLocalStorage } from '../useLocalStorage'; const useSessionInfoState = () => { const [ userInfo, setUserInfo ] = useState(null); const [ userFigure, setUserFigure ] = useState(null); - const [ chatStyleId, setChatStyleId ] = useState(0); + const [ chatStyleId, setChatStyleId ] = useLocalStorage(0); + const [ chatColour, setChatColour ] = useLocalStorage('chatColour', ''); const [ userRespectRemaining, setUserRespectRemaining ] = useState(0); const [ petRespectRemaining, setPetRespectRemaining ] = useState(0); @@ -18,6 +20,11 @@ const useSessionInfoState = () => SendMessageComposer(new RoomUnitChatStyleComposer(styleId)); } + + const updateChatColour = (colour: string) => + { + setChatColour(colour); + } const respectUser = (userId: number) => { @@ -57,7 +64,7 @@ const useSessionInfoState = () => setChatStyleId(parser.chatType); }); - return { userInfo, userFigure, chatStyleId, userRespectRemaining, petRespectRemaining, respectUser, respectPet, updateChatStyleId }; + return { userInfo, userFigure, chatStyleId, userRespectRemaining, petRespectRemaining, respectUser, respectPet, updateChatStyleId, updateChatColour, chatColour }; } -export const useSessionInfo = () => useBetween(useSessionInfoState); +export const useSessionInfo = () => useBetween(useSessionInfoState); \ No newline at end of file diff --git a/src/hooks/useLocalStorage.ts b/src/hooks/useLocalStorage.ts index 1e55fb9..62576e8 100644 --- a/src/hooks/useLocalStorage.ts +++ b/src/hooks/useLocalStorage.ts @@ -2,16 +2,17 @@ import { NitroLogger } from '@nitrots/nitro-renderer'; import { Dispatch, SetStateAction, useState } from 'react'; import { GetLocalStorage, SetLocalStorage } from '../api'; +const userId = new URLSearchParams(window.location.search).get('userid') || 0; + const useLocalStorageState = (key: string, initialValue: T): [ T, Dispatch>] => { + key = userId ? `${ key }.${ userId }` : key; + const [ storedValue, setStoredValue ] = useState(() => { - if(typeof window === 'undefined') return initialValue; - try { - const item = GetLocalStorage(key); - + const item = typeof window !== 'undefined' ? GetLocalStorage(key) as T : undefined; return item ?? initialValue; }