New: Emoji added

This commit is contained in:
duckietm 2024-03-19 13:09:45 +01:00
parent 27c3adbf06
commit d2daaf6087
6 changed files with 135 additions and 286 deletions

View File

@ -0,0 +1,68 @@
import { FC, MouseEvent, useEffect, useRef, useState } from 'react';
import { Overlay, Popover } from 'react-bootstrap';
import { Base, Flex, Grid, NitroCardContentView } from '../../../../common';
import { emojiList } from './EmojiList';
interface ChatInputEmojiSelectorViewProps
{
addChatEmoji: (emoji: string) => void;
}
export const ChatInputEmojiSelectorView: FC<ChatInputEmojiSelectorViewProps> = props =>
{
const { addChatEmoji = null } = props;
const [ selectorVisible, setSelectorVisible ] = useState(false);
const [ target, setTarget ] = useState<(EventTarget & HTMLElement)>(null);
const iconRef = useRef<HTMLDivElement>(null);
const componentRef = useRef<HTMLDivElement>(null);
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<HTMLElement>) =>
{
setSelectorVisible(prevValue => !prevValue);
}
useEffect(() =>
{
if(selectorVisible) {
document.addEventListener('mousedown', handleClickOutside as any);
}
else {
setTarget(null);
}
}, [ componentRef, selectorVisible ]);
return (
<>
<Base pointer onClick={toggleSelector} innerRef={iconRef}>🙂</Base>
<Overlay show={selectorVisible} target={iconRef} placement="top">
<Popover className="nitro-chat-style-selector-container">
<NitroCardContentView overflow="hidden" className="bg-transparent">
<Grid columnCount={4} overflow="auto">
{emojiList && emojiList.length > 0 && emojiList.map((emojiId) => {
return (
<Flex center pointer key={emojiId} onClick={(event) => selectEmoji(emojiId)}>
<Base className="emoji" textColor="black" style={{ fontSize: '20px' }}>{emojiId}</Base>
</Flex>
);
})}
</Grid>
</NitroCardContentView>
</Popover>
</Overlay>
</>
);
}

View File

@ -1,124 +0,0 @@
import { FC, KeyboardEvent, MouseEvent, useEffect, useState } from 'react';
import { Button, Overlay, Popover } from 'react-bootstrap';
import { FaSearch } from 'react-icons/fa';
import { GetSessionDataManager, LocalizeText } from '../../../../api';
import { Base, Flex, Grid, NitroCardContentView } from '../../../../common';
import { useChatInputWidget, useMessenger, useSessionInfo } from '../../../../hooks';
export const ChatInputStickersSelectorConsolaView: FC<{}> = props =>
{
const [ target, setTarget ] = useState<(EventTarget & HTMLElement)>(null);
const [ selectorVisible, setSelectorVisible ] = useState(false);
const [ stickers, setStickers ] = useState(null)
const [ stickersGiphy, setStickersGiphy ] = useState(null)
const [ showNativeStickers, setShowNativeStickers ] = useState(true)
const [ showGiphyStickers, setShowGiphyStickers ] = useState(false)
const { sendChat = null } = useChatInputWidget();
var evadeClosing = false;
const { chatStyleId = 0 } = useSessionInfo();
const { visibleThreads = [], activeThread = null, getMessageThread = null, sendMessage = null, setActiveThreadId = null, closeThread = null } = useMessenger();
function searchStickers(strSearch)
{
evadeClosing = true;
if(strSearch.length == 0){
setShowGiphyStickers(false)
setShowNativeStickers(true)
return;
}
fetch('https://api.giphy.com/v1/gifs/search?api_key=4qwMrfzYMYVl1lhhl3wDaVxDNWAzAv6s&q=' + strSearch + '&limit=25&offset=0&rating=g&lang=en')
.then((response) => response.json())
.then((result) =>
{
setStickersGiphy(result);
setShowNativeStickers(false)
setShowGiphyStickers(true)
})
}
const onKeyDown = (event: KeyboardEvent<HTMLInputElement>) =>
{
if(event.key !== 'Enter') return;
searchStickers(inputText);
}
function sendSticker(sticker){
const stickerArgs = sticker.split(".gif");
sendMessage(activeThread, GetSessionDataManager().userId, stickerArgs[0]);
}
function NativeStickers(){
if(stickers == null) return null;
else return(
<div></div>
);
}
function GiphyStickers(){
if(stickersGiphy == null) return null;
else return(
<Grid columnCount={ 4 } overflow="auto" id="giphyStickers">
{ stickersGiphy.data.map(sticker => (
// eslint-disable-next-line react/jsx-key
<Base key={ sticker.images.downsized_medium.url}>
<img src={ sticker.images.downsized_medium.url } width={ 50 } height={ 50 } className="sticker-img" onClick={ e => sendSticker(sticker.images.fixed_height_small.url) }/>
</Base>
)) }
</Grid>
);
}
const toggleSelector = (event: MouseEvent<HTMLElement>) =>
{
if(evadeClosing) return;
let visible = true;
setSelectorVisible(prevValue =>
{
visible = !prevValue;
return visible;
});
if(visible) setTarget((event.target as (EventTarget & HTMLElement)));
evadeClosing = false;
}
function handleClose(){
evadeClosing = true;
}
var inputText = "";
function onChangeInput(value){
inputText = value;
}
useEffect(() =>
{
if(selectorVisible) return;
setTarget(null);
}, [ selectorVisible ]);
return (
<>
<Base pointer className="icon sticker-icon" onClick={ toggleSelector } style={{ display: "inline-block"}}>
<Overlay show={ selectorVisible } target={ target } placement="top">
<Popover className="nitro-chat-sticker-selector-container image-rendering-pixelated">
<NitroCardContentView overflow="hidden" className="bg-transparent">
<Flex gap={ 1 }>
<input onClick={e => handleClose()} onChange={ e => onChangeInput(e.target.value) } onKeyDown={ onKeyDown } type="text" className="form-control form-control-sm" placeholder={ LocalizeText('generic.search') } />
<Button variant="success" className="stickers-search-button" onClick={e => searchStickers(inputText)}>
<FaSearch className="fa-icon" />
</Button>
</Flex>
{ showNativeStickers ? <NativeStickers /> : null }
{ showGiphyStickers ? <GiphyStickers /> : null }
</NitroCardContentView>
</Popover>
</Overlay>
</Base>
</>
);
}

View File

@ -1,127 +0,0 @@
import { FC, KeyboardEvent, MouseEvent, useEffect, useState } from 'react';
import { Button, Overlay, Popover } from 'react-bootstrap';
import { FaSearch } from 'react-icons/fa';
import { ChatMessageTypeEnum, LocalizeText } from '../../../../api';
import { Base, Flex, Grid, NitroCardContentView } from '../../../../common';
import { useChatInputWidget, useSessionInfo } from '../../../../hooks';
export const ChatInputStickersSelectorView: FC<{}> = props =>
{
const [ target, setTarget ] = useState<(EventTarget & HTMLElement)>(null);
const [ selectorVisible, setSelectorVisible ] = useState(false);
const [ stickers, setStickers ] = useState(null)
const [ stickersGiphy, setStickersGiphy ] = useState(null)
const [ showNativeStickers, setShowNativeStickers ] = useState(true)
const [ showGiphyStickers, setShowGiphyStickers ] = useState(false)
const { sendChat = null } = useChatInputWidget();
var evadeClosing = false;
const { chatStyleId = 0 } = useSessionInfo();
function searchStickers(strSearch)
{
evadeClosing = true;
if(strSearch.length == 0){
setShowGiphyStickers(false)
setShowNativeStickers(true)
return;
}
fetch('https://api.giphy.com/v1/gifs/search?api_key=4qwMrfzYMYVl1lhhl3wDaVxDNWAzAv6s&q=' + strSearch + '&limit=25&offset=0&rating=g&lang=en')
.then((response) => response.json())
.then((result) =>
{
setStickersGiphy(result);
setShowNativeStickers(false)
setShowGiphyStickers(true)
})
}
const onKeyDown = (event: KeyboardEvent<HTMLInputElement>) =>
{
if(event.key !== 'Enter') return;
searchStickers(inputText);
}
function sendSticker(sticker){
const stickerArgs = sticker.split(".gif");
sendChat(stickerArgs[0], ChatMessageTypeEnum.CHAT_DEFAULT , '', chatStyleId);
}
function NativeStickers(){
if(stickers == null) return null;
else return(
<div></div>
);
}
function GiphyStickers(){
if(stickersGiphy == null) return null;
else return(
<Grid columnCount={ 4 } overflow="auto" id="giphyStickers">
{ stickersGiphy.data.map(sticker => (
// eslint-disable-next-line react/jsx-key
<Base key={ sticker.images.downsized_medium.url}>
<img src={ sticker.images.downsized_medium.url } width={ 50 } height={ 50 } className="sticker-img" onClick={ e => sendSticker(sticker.images.fixed_height_small.url) }/>
</Base>
)) }
</Grid>
);
}
const toggleSelector = (event: MouseEvent<HTMLElement>) =>
{
if(evadeClosing) return;
let visible = true;
setSelectorVisible(prevValue =>
{
visible = !prevValue;
return visible;
});
if(visible) setTarget((event.target as (EventTarget & HTMLElement)));
evadeClosing = false;
}
function handleClose(){
evadeClosing = true;
}
var inputText = "";
function onChangeInput(value){
inputText = value;
}
useEffect(() =>
{
if(selectorVisible) return;
setTarget(null);
}, [ selectorVisible ]);
return (
<>
<Base pointer className="icon sticker-icon" onClick={ toggleSelector } style={{ display: "inline-block"}}>
<Overlay show={ selectorVisible } target={ target } placement="top">
<Popover className="nitro-chat-sticker-selector-container image-rendering-pixelated">
<NitroCardContentView overflow="hidden" className="bg-transparent">
<Flex gap={ 1 }>
<input onClick={e => handleClose()} onKeyDown={ onKeyDown } onChange={ e => onChangeInput(e.target.value) } type="text" className="form-control form-control-sm" placeholder={ LocalizeText('generic.search') } />
<Button variant="success" className="stickers-search-button" onClick={e => searchStickers(inputText)}>
<FaSearch className="fa-icon" />
</Button>
</Flex>
{ showNativeStickers ? <NativeStickers /> : null }
{ showGiphyStickers ? <GiphyStickers /> : null }
</NitroCardContentView>
</Popover>
</Overlay>
</Base>
</>
);
}

View File

@ -70,14 +70,62 @@
} }
} }
.nitro-chat-input-container .input-sizer input{
width: calc(100% - 80px)!important;
}
.info-habbopages {
cursor: pointer;
background-image: url("@/assets/images/boxes/card/questionmark.png");
width: 19px;
height: 20px;
position: relative;
&:hover {
background-image: url("@/assets/images/boxes/card/questionmark_hover.png");
&:active {
background-image: url("@/assets/images/boxes/card/questionmark_click.png");
}
}
}
.nitro-chat-style-selector-container { .nitro-chat-style-selector-container {
width: $chat-input-style-selector-widget-width; width: auto;
max-height: $chat-input-style-selector-widget-height; max-height: $chat-input-style-selector-widget-height;
.content-area { .content-area {
max-height: $chat-input-style-selector-widget-height !important; 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 { .bubble-parent-container {
height: 30px; height: 30px;
@ -86,32 +134,4 @@
width: 50px; width: 50px;
} }
} }
} }
.nitro-chat-input-container .input-sizer input{
width: calc(100% - 80px)!important;
}
.sticker-img{
object-fit: contain;
}
#submenuChat{
padding: 5px 10px 1px 3px;
position: absolute;
background-color: rgb(21, 21, 21);
top: 3px;
right: 17px;
display: none;
z-index: 9999999;
border-left: 2px solid rgb(0, 0, 0);
border-radius: 5px;
margin-right: -13px;
}
#submenuChatConsola {
padding: 5px 10px 1px 3px;
background-color: rgb(21, 21, 21);
z-index: 99999;
border-left: 2px solid rgb(0, 0, 0);
border-radius: 5px;
margin-right: 5px;
width: 170px;
display: none;
}

View File

@ -1,10 +1,11 @@
import { HabboClubLevelEnum, RoomControllerLevel } from '@nitrots/nitro-renderer'; import { HabboClubLevelEnum, RoomControllerLevel } from '@nitrots/nitro-renderer';
import { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { createPortal } from 'react-dom'; import { createPortal } from 'react-dom';
import { ChatMessageTypeEnum, GetClubMemberLevel, GetConfiguration, GetSessionDataManager, LocalizeText, RoomWidgetUpdateChatInputContentEvent } from '../../../../api'; import { ChatMessageTypeEnum, CreateLinkEvent, GetClubMemberLevel, GetConfiguration, GetSessionDataManager, LocalizeText, RoomWidgetUpdateChatInputContentEvent } from '../../../../api';
import { Text } from '../../../../common'; import { Base, Flex, Text } from '../../../../common';
import { useChatInputWidget, useRoom, useSessionInfo, useUiEvent } from '../../../../hooks'; import { useChatInputWidget, useRoom, useSessionInfo, useUiEvent } from '../../../../hooks';
import { ChatInputStyleSelectorView } from './ChatInputStyleSelectorView'; import { ChatInputStyleSelectorView } from './ChatInputStyleSelectorView';
import { ChatInputEmojiSelectorView } from './ChatInputEmojiSelectorView';
import { ChatInputColorSelectorView } from './ChatInputColorSelectorView'; import { ChatInputColorSelectorView } from './ChatInputColorSelectorView';
@ -13,6 +14,7 @@ export const ChatInputView: FC<{}> = props =>
const [ chatValue, setChatValue ] = useState<string>(''); const [ chatValue, setChatValue ] = useState<string>('');
const { chatStyleId = 0, updateChatStyleId = null, chatColour = '', updateChatColour = 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 { selectedUsername = '', floodBlocked = false, floodBlockedSeconds = 0, setIsTyping = null, setIsIdle = null, sendChat = null } = useChatInputWidget();
const [ showInfoHabboPages, setShowInfohabboPages ] = useState<boolean>(false);
const { roomSession = null } = useRoom(); const { roomSession = null } = useRoom();
const inputRef = useRef<HTMLInputElement>(); const inputRef = useRef<HTMLInputElement>();
@ -104,7 +106,7 @@ export const ChatInputView: FC<{}> = props =>
} }
setChatValue(append); setChatValue(append);
}, [ chatModeIdWhisper, chatModeIdShout, chatModeIdSpeak, maxChatLength, chatStyleId, setIsTyping, setIsIdle, chatColour ]); }, [ chatModeIdWhisper, chatModeIdShout, chatModeIdSpeak, maxChatLength, chatStyleId, setIsTyping, setIsIdle, sendChat, chatColour ]);
const updateChatInput = useCallback((value: string) => const updateChatInput = useCallback((value: string) =>
{ {
@ -216,6 +218,11 @@ export const ChatInputView: FC<{}> = props =>
return styleIds; return styleIds;
}, []); }, []);
const addEmojiToChat = (emoji: string) =>
{
setChatValue(chatValue + emoji);
setIsTyping(true);
}
useEffect(() => useEffect(() =>
{ {
document.body.addEventListener('keydown', onKeyDownEvent); document.body.addEventListener('keydown', onKeyDownEvent);
@ -237,15 +244,19 @@ export const ChatInputView: FC<{}> = props =>
return ( return (
createPortal( createPortal(
<div className="nitro-chat-input-container"> <div className="nitro-chat-input-container" onMouseEnter={ () => setShowInfohabboPages(true) } onMouseLeave={ () => setTimeout(() => setShowInfohabboPages(false), 100) }>
<div className="input-sizer align-items-center"> <div className="input-sizer align-items-center">
{ !floodBlocked && { !floodBlocked &&
<input ref={ inputRef } type="text" className="chat-input" placeholder={ LocalizeText('widgets.chatinput.default') } value={ chatValue } maxLength={ maxChatLength } onChange={ event => updateChatInput(event.target.value) } onMouseDown={ event => setInputFocus() } /> } <input ref={ inputRef } type="text" className="chat-input" placeholder={ LocalizeText('widgets.chatinput.default') } value={ chatValue } maxLength={ maxChatLength } onChange={ event => updateChatInput(event.target.value) } onMouseDown={ event => setInputFocus() } /> }
{ floodBlocked && { floodBlocked &&
<Text variant="danger">{ LocalizeText('chat.input.alert.flood', [ 'time' ], [ floodBlockedSeconds.toString() ]) } </Text> } <Text variant="danger">{ LocalizeText('chat.input.alert.flood', [ 'time' ], [ floodBlockedSeconds.toString() ]) } </Text> }
</div> </div>
<ChatInputEmojiSelectorView addChatEmoji={ addEmojiToChat } />
<ChatInputColorSelectorView chatColour={ chatColour } selectColour={ updateChatColour } /> <ChatInputColorSelectorView chatColour={ chatColour } selectColour={ updateChatColour } />
<ChatInputStyleSelectorView chatStyleId={ chatStyleId } chatStyleIds={ chatStyleIds } selectChatStyleId={ updateChatStyleId } /> <ChatInputStyleSelectorView chatStyleId={ chatStyleId } chatStyleIds={ chatStyleIds } selectChatStyleId={ updateChatStyleId } />
</div>, document.getElementById('toolbar-chat-input-container')) { (showInfoHabboPages) &&
<Base className="info-habbopages" onClick={ () => CreateLinkEvent('habbopages/chat/chatting') }></Base>
}
</div>, document.getElementById('toolbar-chat-input-container'))
); );
} }

File diff suppressed because one or more lines are too long