Add chat style selector

This commit is contained in:
Bill 2021-09-30 00:38:02 -04:00
parent a9d4bd1559
commit 823ec8c650
6 changed files with 158 additions and 93 deletions

View File

@ -1,13 +1,3 @@
@media only screen and (max-width: 600px) {
.nitro-chat-input {
position: fixed;
left: 50%;
transform: translateX(-50%);
bottom: 65px !important;
z-index: $chatinput-zindex;
}
}
.nitro-chat-input-container { .nitro-chat-input-container {
display: flex; display: flex;
justify-content: center; justify-content: center;
@ -17,7 +7,7 @@
border-radius: 8px; border-radius: 8px;
border: 2px solid rgb(0, 0, 0); border: 2px solid rgb(0, 0, 0);
background: #EDEDED; background: #EDEDED;
padding-right: 30px; padding-right: 10px;
width: 100%; width: 100%;
&:before { &:before {
@ -60,6 +50,40 @@
white-space: pre-wrap; white-space: pre-wrap;
} }
} }
.bubble-container {
visibility: visible;
width: 75%;
}
} }
@import './style-selector/ChatInputStyleSelectorView'; .nitro-chat-style-selector-button {
position: absolute;
display: flex;
justify-content: center;
align-items: center;
height: 100%;
right: 7px;
}
.nitro-chat-style-selector-container {
width: $chat-input-style-selector-widget-width;
height: $chat-input-style-selector-widget-height;
.grid-item {
font-size: $font-size-sm;
height: 30px !important;
border-color: unset !important;
border: 0 !important;
padding: 1px 3px;
&:not(.active) {
background-color: unset !important;
}
.bubble-container {
visibility: visible;
width: 75%;
}
}
}

View File

@ -1,3 +1,4 @@
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 { GetConfiguration, GetSessionDataManager, LocalizeText, RoomWidgetChatMessage, RoomWidgetChatTypingMessage, RoomWidgetRoomObjectUpdateEvent, RoomWidgetUpdateChatInputContentEvent, RoomWidgetUpdateInfostandUserEvent } from '../../../../api'; import { GetConfiguration, GetSessionDataManager, LocalizeText, RoomWidgetChatMessage, RoomWidgetChatTypingMessage, RoomWidgetRoomObjectUpdateEvent, RoomWidgetUpdateChatInputContentEvent, RoomWidgetUpdateInfostandUserEvent } from '../../../../api';
@ -117,6 +118,7 @@ export const ChatInputView: FC<{}> = props =>
{ {
if(needsChatStyleUpdate) if(needsChatStyleUpdate)
{ {
console.log('send')
GetSessionDataManager().sendChatStyleUpdate(chatStyleId); GetSessionDataManager().sendChatStyleUpdate(chatStyleId);
setNeedsChatStyleUpdate(false); setNeedsChatStyleUpdate(false);
@ -175,12 +177,6 @@ export const ChatInputView: FC<{}> = props =>
}, [ inputRef, chatModeIdWhisper, anotherInputHasFocus, setInputFocus, checkSpecialKeywordForInput, sendChatValue ]); }, [ inputRef, chatModeIdWhisper, anotherInputHasFocus, setInputFocus, checkSpecialKeywordForInput, sendChatValue ]);
const onStyleSelected = useCallback((styleId: number) =>
{
setChatStyleId(styleId);
setNeedsChatStyleUpdate(true);
}, []);
const onRoomWidgetRoomObjectUpdateEvent = useCallback((event: RoomWidgetRoomObjectUpdateEvent) => const onRoomWidgetRoomObjectUpdateEvent = useCallback((event: RoomWidgetRoomObjectUpdateEvent) =>
{ {
setSelectedUsername(''); setSelectedUsername('');
@ -210,6 +206,61 @@ export const ChatInputView: FC<{}> = props =>
CreateEventDispatcherHook(RoomWidgetUpdateChatInputContentEvent.CHAT_INPUT_CONTENT, eventDispatcher, onRoomWidgetChatInputContentUpdateEvent); CreateEventDispatcherHook(RoomWidgetUpdateChatInputContentEvent.CHAT_INPUT_CONTENT, eventDispatcher, onRoomWidgetChatInputContentUpdateEvent);
const selectChatStyleId = useCallback((styleId: number) =>
{
setChatStyleId(styleId);
setNeedsChatStyleUpdate(true);
}, []);
const chatStyleIds = useMemo(() =>
{
let styleIds: number[] = [];
const styles = GetConfiguration<{ 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(GetConfiguration<number[]>('chat.styles.disabled').indexOf(style.styleId) >= 0) continue;
if(style.isHcOnly && (GetSessionDataManager().clubLevel >= 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(() => useEffect(() =>
{ {
if(isTyping) if(isTyping)
@ -273,7 +324,7 @@ export const ChatInputView: FC<{}> = props =>
<div className="input-sizer"> <div className="input-sizer">
<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() } />
</div> </div>
<ChatInputStyleSelectorView onStyleSelected={ onStyleSelected } /> <ChatInputStyleSelectorView chatStyleId={ chatStyleId } chatStyleIds={ chatStyleIds } selectChatStyleId={ selectChatStyleId } />
</div>, document.getElementById('toolbar-chat-input-container')) </div>, document.getElementById('toolbar-chat-input-container'))
); );
} }

View File

@ -1,61 +0,0 @@
.nitro-chat-style-selector-button {
position: absolute;
display: flex;
justify-content: center;
align-items: center;
height: 100%;
right: 7px;
}
.nitro-chat-style-selector-test {
display: flex;
position: relative;
right: 30px;
pointer-events: all;
height: 100%;
i.icon {
cursor: pointer;
align-self: center;
}
.nitro-chatstyle-selector {
position: absolute;
width: 250px;
top: -4px;
transition: transform 0.22s ease-in-out;
transform: translate(-81px, -50%) scale(0);
&.active {
visibility: visible;
transform: translate(-160px, -100%) scale(1);
}
.grid-container {
.grid-items {
margin-top: -7px;
.item-detail {
height: 30px;
max-height: 30px;
width: calc(1 / 3 * 100% - (1 - 1 / 3) * 7px);
margin: 7px 7px 0 0;
overflow: visible;
&:hover {
cursor: pointer;
}
.detail-info {
.bubble-container {
visibility: visible;
width: 75%;
}
}
}
}
}
}
}

View File

@ -1,20 +1,69 @@
import { FC, useState } from 'react'; import { FC, MouseEvent, useCallback, useEffect, useState } from 'react';
import { Overlay, Popover } from 'react-bootstrap';
import { BatchUpdates } from '../../../../../hooks';
import { NitroCardContentView, NitroCardGridItemView, NitroCardGridView } from '../../../../../layout';
import { ChatInputStyleSelectorViewProps } from './ChatInputStyleSelectorView.types'; import { ChatInputStyleSelectorViewProps } from './ChatInputStyleSelectorView.types';
export const ChatInputStyleSelectorView: FC<ChatInputStyleSelectorViewProps> = props => export const ChatInputStyleSelectorView: FC<ChatInputStyleSelectorViewProps> = props =>
{ {
const { onStyleSelected = null } = props; const { chatStyleId = 0, chatStyleIds = null, selectChatStyleId = null } = props;
const [ target, setTarget ] = useState<(EventTarget & HTMLElement)>(null);
const [ selectorVisible, setSelectorVisible ] = useState(false); const [ selectorVisible, setSelectorVisible ] = useState(false);
useEffect(() =>
{
if(selectorVisible) return;
setTarget(null);
}, [ selectorVisible ]);
const selectStyle = (styleId: number) =>
{
BatchUpdates(() =>
{
selectChatStyleId(styleId);
setSelectorVisible(false);
});
}
const toggleSelector = useCallback((event: MouseEvent<HTMLElement>) =>
{
BatchUpdates(() =>
{
let visible = false;
setSelectorVisible(prevValue =>
{
visible = !prevValue;
return visible;
});
if(visible) setTarget((event.target as (EventTarget & HTMLElement)));
})
}, []);
return ( return (
<> <>
<div className="nitro-chat-style-selector-button"> <i className="icon chatstyles-icon cursor-pointer" onClick={ toggleSelector } />
<i className="icon chatstyles-icon" /> <Overlay show={ selectorVisible } target={ target } placement="top">
<Popover className="nitro-chat-style-selector-container" id="chat-style-selector">
<NitroCardContentView className="bg-transparent">
<NitroCardGridView>
{ chatStyleIds && (chatStyleIds.length > 0) && chatStyleIds.map((styleId) =>
{
return (
<NitroCardGridItemView key={ styleId } itemActive={ (chatStyleId === styleId) } onClick={ event => selectStyle(styleId) }>
<div className="bubble-container">
<div className={ 'w-100 chat-bubble bubble-' + styleId }>&nbsp;</div>
</div> </div>
{ selectorVisible && </NitroCardGridItemView>
<div className="nitro-chat-style-selector-container"> );
}) }
</div> } </NitroCardGridView>
</NitroCardContentView>
</Popover>
</Overlay>
</> </>
) );
} }

View File

@ -1,4 +1,6 @@
export interface ChatInputStyleSelectorViewProps export interface ChatInputStyleSelectorViewProps
{ {
onStyleSelected: (styleId: number) => void; chatStyleId: number;
chatStyleIds: number[];
selectChatStyleId: (styleId: number) => void;
} }

View File

@ -11,6 +11,6 @@
border-radius: 0; border-radius: 0;
box-shadow: none; box-shadow: none;
pointer-events: none; pointer-events: none;
}
@import './message/ChatWidgetMessageView'; @import './message/ChatWidgetMessageView';
}