import { 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 { useChatCommandSelector, useChatInputWidget, useRoom, useSessionInfo, useUiEvent } from '../../../../hooks'; import { ChatInputCommandSelectorView } from './ChatInputCommandSelectorView'; import { ChatInputEmojiSelectorView } from './ChatInputEmojiSelectorView'; import { ChatInputStyleSelectorView } from './ChatInputStyleSelectorView'; export const ChatInputView: FC = () => { const [ chatValue, setChatValue ] = useState(''); const { chatStyleId = 0, updateChatStyleId = null } = useSessionInfo(); const { selectedUsername = '', floodBlocked = false, floodBlockedSeconds = 0, setIsTyping = null, setIsIdle = null, sendChat = null } = useChatInputWidget(); const { roomSession = null } = useRoom(); const inputRef = useRef(null); const { isVisible: commandSelectorVisible, filteredCommands, selectedIndex, setSelectedIndex, moveUp, moveDown, selectCurrent, close: closeCommandSelector } = useChatCommandSelector(chatValue); const chatModeIdWhisper = useMemo(() => LocalizeText('widgets.chatinput.mode.whisper'), []); const chatModeIdShout = useMemo(() => LocalizeText('widgets.chatinput.mode.shout'), []); const chatModeIdSpeak = useMemo(() => LocalizeText('widgets.chatinput.mode.speak'), []); const maxChatLength = useMemo(() => GetConfigurationValue('chat.input.maxlength', 100), []); const anotherInputHasFocus = useCallback(() => { const activeElement = document.activeElement; if(!activeElement) return false; if(inputRef && (inputRef.current === activeElement)) return false; if(!(activeElement instanceof HTMLInputElement) && !(activeElement instanceof HTMLTextAreaElement)) return false; return true; }, [ inputRef ]); const setInputFocus = useCallback(() => { inputRef.current.focus(); inputRef.current.setSelectionRange((inputRef.current.value.length * 2), (inputRef.current.value.length * 2)); }, [ inputRef ]); const setChatInputValue = useCallback((value: string, markTyping: boolean = true) => { setChatValue(value); if(markTyping) { setIsTyping(!!value.length); setIsIdle(!!value.length); } requestAnimationFrame(() => { inputRef.current?.focus(); inputRef.current?.setSelectionRange(value.length, value.length); }); }, [ setIsTyping, setIsIdle ]); const checkSpecialKeywordForInput = useCallback(() => { setChatValue(prevValue => { if((prevValue !== chatModeIdWhisper) || !selectedUsername.length) return prevValue; return (`${ prevValue } ${ selectedUsername }`); }); }, [ selectedUsername, chatModeIdWhisper ]); const sendChatValue = useCallback((value: string, shiftKey: boolean = false) => { if(!value || (value === '')) return; let chatType = (shiftKey ? ChatMessageTypeEnum.CHAT_SHOUT : ChatMessageTypeEnum.CHAT_DEFAULT); let text = value; const parts = text.split(' '); let recipientName = ''; let append = ''; switch(parts[0]) { case chatModeIdWhisper: chatType = ChatMessageTypeEnum.CHAT_WHISPER; recipientName = parts[1]; append = (chatModeIdWhisper + ' ' + recipientName + ' '); parts.shift(); parts.shift(); break; case chatModeIdShout: chatType = ChatMessageTypeEnum.CHAT_SHOUT; parts.shift(); break; case chatModeIdSpeak: chatType = ChatMessageTypeEnum.CHAT_DEFAULT; parts.shift(); break; } text = parts.join(' '); setIsTyping(false); setIsIdle(false); if(text.length <= maxChatLength) { if(/%CC%/g.test(encodeURIComponent(text))) { setChatValue(''); } else { setChatValue(''); sendChat(text, chatType, recipientName, chatStyleId); } } setChatValue(append); }, [ chatModeIdWhisper, chatModeIdShout, chatModeIdSpeak, maxChatLength, chatStyleId, setIsTyping, setIsIdle, sendChat ]); const updateChatInput = useCallback((value: string) => { if(!value || !value.length) { setIsTyping(false); } else { setIsTyping(true); setIsIdle(true); } setChatValue(value); }, [ setIsTyping, setIsIdle ]); const addChatEmoji = useCallback((emoji: string) => { setChatValue(prev => prev + emoji); setIsTyping(true); inputRef.current?.focus(); }, [ setIsTyping, inputRef ]); const onKeyDownEvent = useCallback((event: KeyboardEvent) => { if(floodBlocked || !inputRef.current || anotherInputHasFocus()) return; if(document.activeElement !== inputRef.current) setInputFocus(); if(commandSelectorVisible) { switch(event.key) { case 'ArrowUp': event.preventDefault(); moveUp(); return; case 'ArrowDown': event.preventDefault(); moveDown(); return; case 'Tab': event.preventDefault(); // fall through case 'NumpadEnter': case 'Enter': { const selected = selectCurrent(); if(selected) { event.preventDefault(); setChatInputValue(':' + selected.key + ' '); return; } break; } case 'Escape': event.preventDefault(); closeCommandSelector(); return; } } const value = (event.target as HTMLInputElement).value; switch(event.key) { case ' ': case 'Space': checkSpecialKeywordForInput(); return; case 'NumpadEnter': case 'Enter': sendChatValue(value, event.shiftKey); return; case 'Backspace': if(value) { const parts = value.split(' '); if((parts[0] === chatModeIdWhisper) && (parts.length === 3) && (parts[2] === '')) { setChatValue(''); } } return; } }, [ floodBlocked, inputRef, chatModeIdWhisper, anotherInputHasFocus, setInputFocus, checkSpecialKeywordForInput, sendChatValue, commandSelectorVisible, moveUp, moveDown, selectCurrent, setChatInputValue, closeCommandSelector ]); useUiEvent(RoomWidgetUpdateChatInputContentEvent.CHAT_INPUT_CONTENT, event => { switch(event.chatMode) { case RoomWidgetUpdateChatInputContentEvent.TEXT: setChatInputValue(event.userName); return; case RoomWidgetUpdateChatInputContentEvent.WHISPER: { setChatValue(`${ chatModeIdWhisper } ${ event.userName } `); return; } case RoomWidgetUpdateChatInputContentEvent.SHOUT: return; } }); const chatStyleIds = useMemo(() => { let styleIds: number[] = []; const styles = GetConfigurationValue<{ styleId: number, minRank: number, isSystemStyle: boolean, isHcOnly: boolean, isAmbassadorOnly: boolean }[]>('chat.styles'); for(const style of styles) { if(!style) continue; if(style.minRank > 0) { if(GetSessionDataManager().hasSecurity(style.minRank)) styleIds.push(style.styleId); continue; } if(style.isSystemStyle) { if(GetSessionDataManager().hasSecurity(RoomControllerLevel.MODERATOR)) { styleIds.push(style.styleId); continue; } } if(GetConfigurationValue('chat.styles.disabled').indexOf(style.styleId) >= 0) continue; if(style.isHcOnly && (GetClubMemberLevel() >= HabboClubLevelEnum.CLUB)) { styleIds.push(style.styleId); continue; } if(style.isAmbassadorOnly && GetSessionDataManager().isAmbassador) { styleIds.push(style.styleId); continue; } if(!style.isHcOnly && !style.isAmbassadorOnly) styleIds.push(style.styleId); } return styleIds; }, []); useEffect(() => { document.body.addEventListener('keydown', onKeyDownEvent); return () => { document.body.removeEventListener('keydown', onKeyDownEvent); }; }, [ onKeyDownEvent ]); useEffect(() => { if(!inputRef.current) return; inputRef.current.parentElement.dataset.value = chatValue; }, [ chatValue ]); if(!roomSession || roomSession.isSpectator) return null; return ( createPortal(
{ commandSelectorVisible && { setChatInputValue(':' + cmd.key + ' '); } } onHover={ setSelectedIndex } /> }
{ !floodBlocked && updateChatInput(event.target.value) } onMouseDown={ event => setInputFocus() } /> } { floodBlocked && { LocalizeText('chat.input.alert.flood', [ 'time' ], [ floodBlockedSeconds.toString() ]) } }
, document.getElementById('toolbar-chat-input-container')) ); };