mirror of
https://github.com/duckietm/Nitro-Cool-UI.git
synced 2025-06-21 22:36:58 +00:00
Added: ChatColors / Make stuff LocalCache in browser
This commit is contained in:
parent
c00f4329d3
commit
e1bcafbb28
@ -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
|
||||
|
@ -1,4 +1,5 @@
|
||||
const allowedColours: Map<string, string> = new Map();
|
||||
import { GetConfiguration } from '@nitrots/nitro-renderer';
|
||||
export const allowedColours: Map<string, string> = 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, '<span class="chat-tag"><b>$1</b></span>');
|
||||
|
||||
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 = '<span style="color: ' + color + '">' + text + '</span>';
|
||||
}
|
||||
break;
|
||||
}
|
||||
// Youtube link
|
||||
if (!GetConfiguration<boolean>('youtube.publish.disabled', false)) {
|
||||
content = content.replace(
|
||||
/(?:http:\/\/|https:\/\/)?(?:www\.)?(?:youtube\.com|youtu\.be)\/(?:watch\?.*v=|shorts\/)?([a-zA-Z0-9_-]{11})/g,
|
||||
`
|
||||
<div>
|
||||
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAACXBIWXMAAAsTAAALEwEAmpwYAAAEQElEQVR4nO2X7WscRRzHt+AbfeEfICK+VVTwjWhVUnI7l8SkDY3Wu5ltfbiZXBNbq8ldiZEkVbHSIAEjCtUWNLXFaNWQElOUomKwD0bxoVLtk3I+oPhUjbvZ5x2Zmb29DUlaa3fTO9gvDOzMbx5+n535zYMkJUqUKFFNKoVIK0CEyogcKZfJCO/2y3qkaldLPn+ZDLEGELYbFeVyVgYgLnGADL5GqgUBRCaYwwC1NzUq5EruPCQnpVpRGpG87/RgGhJFLB88xGzyuvxVAOGtAOIXmU2SpGWsHKzNXcfaySh/Pe9DyeVSkNxzUQDkbO4KgIjH4kBGZLuAaa/j5ZD8LmZHJBniAd4Gtj8kynCXyBNVRuRX6WIJIDLN4gBA/CNzuq5uyyWhmRllQL69VJUAMsQDlT+NR3ynHhXLKVfkkJCcAZDoVQmQyuZuLAOkYPudNQcgSdIysZ0SWq/cf20tAkgyJDMJQHXNAN7ox8WTN6/pulRG2AAI/xZeQjLEj7ATG0BsVd0Sqs/iG2REHO64f70ACL/BbKEtVpUh+VvUqzIAJqDgewEip5lNRmQfO9xYeUNm/dUAkZ8AwidkmL+tKoI40VLJeGZbv97dWdIIVLVcRtOUVlPNrrR4urvZVlvrvSClb6Fq6iaR2HfYxur67TSl1eR9Eaiyvo1nB/ticV7vL04HDsWc9P5i8EiKRObw4GNL5bzqJzbbkQHo3R0/sE7dE99Qb2ZmXnK++HRRR6zxvbyO9fru85uFro7vIwPQcFZlnXq//EwXkvvtqUUdsQ/s53Xst8fOC0DLZdToANa1Gfyv9DxIjSd6qVv6Tjh+/Gue14sPRA+wdrURGYCaXWmHO3e+/Iw75Rz8UEx3X4Far45Qvb9AtUwz/7Z27ZwLMPUeNV9+gVqju6i2GlTW+uDj1Jl6n9ofHOD9BONkW+zoANrS7tkA7Mlx4eT+fVTvWi/WlefOAQjL+Ui0M3c8P8+mFzeIcdrSbnQALSu8CwVwjx0N6nnqP9zm/fmHaPfOBHVPnwz64OO0rPCiA2hYTi8UgMWA3vuwb/Ooesftlb9e6KTWm6Oiz+lDYpyG5TQ6gPCpGhVAc10A4GkapabpAxyOASCCJXQ2gLCcQ1MxLKEIgngOAKVzAPTuDrZtUn3zRjpLYAxBfI5t9H8BhGOguIFab/kx8PHB6LfR8kF2LgD31HG+2ywE4Bz9nFp7XhI22xK70F9neJYFsHvsK2GaHI/+ICtfJRYDsF7ZWVnEtr0gQFjMWd5u7555Nr1nU/RXifJlrpzM54aoPTFGjaGnxGB3NVL73Ul+cZvdRLjNGntNnLTbtvC8uX2Y/2m258/et8bfaW6l1sgODuR8coQaW/viuczV/HWaiT0ylsp5fWDzYSkOseeeXgg9KeEqq/KkbHL++5OyyQmelHCVFTwpC50lY/jp3licT5QoUSIpDv0L7jL5ksuHFDUAAAAASUVORK5CYII="
|
||||
alt="YouTube Icon" style="vertical-align: middle; margin-right: 5px;"><strong>Click on open video to see the YouTube video</strong>
|
||||
</div><center><a href="https://youtu.be/$1" target="_blank" style="background-color: red; color: white; padding: 5px 10px; border-radius: 5px; text-decoration: none;">Open Video</a></center>
|
||||
`
|
||||
);
|
||||
}
|
||||
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 = `<span style="color: ${color}">${text}</span>`;
|
||||
}
|
||||
} else {
|
||||
result = content;
|
||||
}
|
||||
|
||||
|
@ -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<ChatInputColorSelectorViewProps> = props =>
|
||||
{
|
||||
const { chatColour = 'black', selectColour = null } = props;
|
||||
const [ selectorVisible, setSelectorVisible ] = useState(false);
|
||||
const [ colours, setColours ] = useState<Map<string, string>>(null);
|
||||
const iconRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
const excludedColors = new Set(["r", "b", "g", "y", "w", "o", "c", "br", "pr", "pk"]);
|
||||
const uniqueColours = new Map<string, string>();
|
||||
|
||||
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 (
|
||||
<>
|
||||
<Base pointer onClick={ () => toggleSelector() } innerRef={ iconRef } style={ { color: (colours && colours.get(chatColour)) ?? 'black' } }>
|
||||
<MdFormatColorText />
|
||||
</Base>
|
||||
<Overlay show={ selectorVisible } target={ iconRef } placement="top">
|
||||
<Popover className="nitro-chat-style-selector-container">
|
||||
<NitroCardContentView overflow="hidden" className="bg-transparent colour-container image-rendering-pixelated">
|
||||
<AutoGrid gap={ 1 } columnCount={ 6 } columnMinWidth={ 20 } columnMinHeight={ 20 }>
|
||||
{ colours && (colours.size > 0) && Array.from(colours).map(([ color, hex ]) =>
|
||||
{
|
||||
return (
|
||||
<LayoutGridItem itemHighlight itemColor={ hex } itemActive={ chatColour === color } className="clear-bg" onClick={ event => selectColor(color) } key={ color }></LayoutGridItem>
|
||||
);
|
||||
}) }
|
||||
</AutoGrid>
|
||||
</NitroCardContentView>
|
||||
</Popover>
|
||||
</Overlay>
|
||||
</>
|
||||
);
|
||||
}
|
@ -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<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 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<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-end">
|
||||
<Popover>
|
||||
<Picker data={data} onEmojiSelect={handleEmojiSelect} />
|
||||
</Popover>
|
||||
</Overlay>
|
||||
</>
|
||||
);
|
||||
}
|
@ -46,9 +46,9 @@ export const ChatInputStyleSelectorView: FC<ChatInputStyleSelectorViewProps> = p
|
||||
<>
|
||||
<Base pointer className="icon chatstyles-icon" onClick={ toggleSelector } />
|
||||
<Overlay show={ selectorVisible } target={ target } placement="top">
|
||||
<Popover className="nitro-chat-style-selector-container image-rendering-pixelated">
|
||||
<NitroCardContentView overflow="hidden" className="bg-transparent">
|
||||
<Grid columnCount={ 3 } overflow="auto">
|
||||
<Popover className="nitro-chat-style-selector-container">
|
||||
<NitroCardContentView overflow="hidden" className="bg-transparent bubble-window image-rendering-pixelated">
|
||||
<Grid gap={ 1 } columnCount={ 3 } overflow="auto">
|
||||
{ chatStyleIds && (chatStyleIds.length > 0) && chatStyleIds.map((styleId) =>
|
||||
{
|
||||
return (
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<string>('');
|
||||
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<boolean>(false);
|
||||
const { roomSession = null } = useRoom();
|
||||
const inputRef = useRef<HTMLInputElement>();
|
||||
|
||||
const inputRef = useRef<HTMLInputElement>();
|
||||
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(
|
||||
<div className="nitro-chat-input-container">
|
||||
<div className="input-sizer align-items-center">
|
||||
<div className="nitro-chat-input-container" onMouseEnter={ () => setShowInfohabboPages(true) } onMouseLeave={ () => setTimeout(() => setShowInfohabboPages(false), 100) }>
|
||||
<div className="input-sizer align-items-center">
|
||||
{ !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 &&
|
||||
<Text variant="danger">{ LocalizeText('chat.input.alert.flood', [ 'time' ], [ floodBlockedSeconds.toString() ]) } </Text> }
|
||||
</div>
|
||||
<ChatInputStyleSelectorView chatStyleId={ chatStyleId } chatStyleIds={ chatStyleIds } selectChatStyleId={ updateChatStyleId } />
|
||||
</div>, document.getElementById('toolbar-chat-input-container'))
|
||||
<Flex>
|
||||
<ChatInputEmojiSelectorView addChatEmoji={ addEmojiToChat } />
|
||||
<ChatInputColorSelectorView chatColour={ chatColour } selectColour={ updateChatColour } />
|
||||
<ChatInputStyleSelectorView chatStyleId={ chatStyleId } chatStyleIds={ chatStyleIds } selectChatStyleId={ updateChatStyleId } />
|
||||
{
|
||||
(showInfoHabboPages) && <Base className="info-habbopages" onClick={ () => CreateLinkEvent('habbopages/chat/chatting') }></Base>
|
||||
}
|
||||
</Flex>
|
||||
</div>, document.getElementById('toolbar-chat-input-container'))
|
||||
);
|
||||
}
|
||||
}
|
@ -7,11 +7,11 @@ interface ChatWidgetMessageViewProps
|
||||
chat: ChatBubbleMessage;
|
||||
makeRoom: (chat: ChatBubbleMessage) => void;
|
||||
bubbleWidth?: number;
|
||||
selectedEmoji?: string;
|
||||
}
|
||||
|
||||
export const ChatWidgetMessageView: FC<ChatWidgetMessageViewProps> = props =>
|
||||
{
|
||||
const { chat = null, makeRoom = null, bubbleWidth = RoomChatSettings.CHAT_BUBBLE_WIDTH_NORMAL } = props;
|
||||
export const ChatWidgetMessageView: FC<ChatWidgetMessageViewProps> = props => {
|
||||
const { chat = null, makeRoom = null, bubbleWidth = RoomChatSettings.CHAT_BUBBLE_WIDTH_NORMAL, selectedEmoji } = props;
|
||||
const [ isVisible, setIsVisible ] = useState(false);
|
||||
const [ isReady, setIsReady ] = useState<boolean>(false);
|
||||
const elementRef = useRef<HTMLDivElement>();
|
||||
@ -76,8 +76,9 @@ export const ChatWidgetMessageView: FC<ChatWidgetMessageViewProps> = props =>
|
||||
}, [ chat, isReady, isVisible, makeRoom ]);
|
||||
|
||||
return (
|
||||
<div ref={ elementRef } className={ `bubble-container ${ isVisible ? 'visible' : 'invisible' }` } onClick={ event => GetRoomEngine().selectRoomObject(chat.roomId, chat.senderId, RoomObjectCategory.UNIT) }>
|
||||
{ (chat.styleId === 0) &&
|
||||
<div ref={elementRef} className={`bubble-container newbubblehe ${isVisible ? 'visible' : 'invisible'}`} onClick={event => GetRoomEngine().selectRoomObject(chat.roomId, chat.senderId, RoomObjectCategory.UNIT)}>
|
||||
{selectedEmoji && <span>{DOMPurify.sanitize(selectedEmoji)}</span>}
|
||||
{ (chat.styleId === 0) &&
|
||||
<div className="user-container-bg" style={ { backgroundColor: chat.color } } /> }
|
||||
<div className={ `chat-bubble bubble-${ chat.styleId } type-${ chat.type }` } style={ { maxWidth: getBubbleWidth } }>
|
||||
<div className="user-container">
|
||||
@ -86,10 +87,10 @@ export const ChatWidgetMessageView: FC<ChatWidgetMessageViewProps> = props =>
|
||||
</div>
|
||||
<div className="chat-content">
|
||||
<b className="username mr-1" dangerouslySetInnerHTML={ { __html: `${ chat.username }: ` } } />
|
||||
<span className="message" dangerouslySetInnerHTML={ { __html: `${ chat.formattedText }` } } />
|
||||
<span className="message" style={{ color: chat.chatColours }} dangerouslySetInnerHTML={{ __html: `${chat.formattedText}` }} onClick={e => onClickChat(e)} />
|
||||
</div>
|
||||
<div className="pointer" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
@ -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 &&
|
||||
<Base pointer className="navigation-item icon icon-house click-box" onClick={ event => CreateLinkEvent('navigator/goto/home') } /> }
|
||||
<Base pointer className="navigation-item icon icon-rooms click-box" onClick={ event => CreateLinkEvent('navigator/toggle') } />
|
||||
{ GetConfiguration('game.center.enabled') && <Base pointer className="navigation-item icon icon-game click-box" onClick={ event => CreateLinkEvent('games/toggle') } /> }
|
||||
{ GetConfigurationValue('game.center.enabled') && <Base pointer className="navigation-item icon icon-game click-box" onClick={ event => CreateLinkEvent('games/toggle') } /> }
|
||||
<Base pointer className="navigation-item icon icon-catalog click-box" onClick={ event => CreateLinkEvent('catalog/toggle') } />
|
||||
<Base pointer className="navigation-item icon icon-inventory click-box" onClick={ event => CreateLinkEvent('inventory/toggle') }>
|
||||
{ (getFullCount > 0) &&
|
||||
|
@ -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<IChatEntry[]>([]);
|
||||
const [ roomHistory, setRoomHistory ] = useState<IRoomHistoryEntry[]>([]);
|
||||
const [ messengerHistory, setMessengerHistory ] = useState<IChatEntry[]>([]);
|
||||
const [ needsRoomInsert, setNeedsRoomInsert ] = useState(false);
|
||||
const [ chatHistory, setChatHistory ] = useLocalStorage<IChatEntry[]>('chatHistory', []);
|
||||
const [ roomHistory, setRoomHistory ] = useLocalStorage<IRoomHistoryEntry[]>('roomHistory', []);
|
||||
const [ messengerHistory, setMessengerHistory ] = useLocalStorage<IChatEntry[]>('messengerHistory', []);
|
||||
const [ needsRoomInsert, setNeedsRoomInsert ] = useLocalStorage('needsRoomInsert', false);
|
||||
|
||||
const addChatEntry = (entry: IChatEntry) =>
|
||||
{
|
||||
|
4
src/hooks/events/useRoomSessionManagerEvent.tsx
Normal file
4
src/hooks/events/useRoomSessionManagerEvent.tsx
Normal file
@ -0,0 +1,4 @@
|
||||
import { GetRoomSessionManager, NitroEvent } from '@nitrots/nitro-renderer';
|
||||
import { useEventDispatcher } from './useEventDispatcher';
|
||||
|
||||
export const useRoomSessionManagerEvent = <T extends NitroEvent>(type: string | string[], handler: (event: T) => void) => useEventDispatcher(type, GetRoomSessionManager().events, handler);
|
@ -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 =>
|
||||
{
|
||||
|
@ -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<string>(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>(RoomDragEvent.ROOM_DRAG, event =>
|
||||
@ -188,4 +192,4 @@ const useChatWidgetState = () =>
|
||||
return { chatMessages, setChatMessages, chatSettings, getScrollSpeed };
|
||||
}
|
||||
|
||||
export const useChatWidget = useChatWidgetState;
|
||||
export const useChatWidget = useChatWidgetState;
|
@ -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<UserInfoDataParser>(null);
|
||||
const [ userFigure, setUserFigure ] = useState<string>(null);
|
||||
const [ chatStyleId, setChatStyleId ] = useState<number>(0);
|
||||
const [ chatStyleId, setChatStyleId ] = useLocalStorage<number>(0);
|
||||
const [ chatColour, setChatColour ] = useLocalStorage<string>('chatColour', '');
|
||||
const [ userRespectRemaining, setUserRespectRemaining ] = useState<number>(0);
|
||||
const [ petRespectRemaining, setPetRespectRemaining ] = useState<number>(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);
|
@ -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 = <T>(key: string, initialValue: T): [ T, Dispatch<SetStateAction<T>>] =>
|
||||
{
|
||||
key = userId ? `${ key }.${ userId }` : key;
|
||||
|
||||
const [ storedValue, setStoredValue ] = useState<T>(() =>
|
||||
{
|
||||
if(typeof window === 'undefined') return initialValue;
|
||||
|
||||
try
|
||||
{
|
||||
const item = GetLocalStorage<T>(key);
|
||||
|
||||
const item = typeof window !== 'undefined' ? GetLocalStorage<T>(key) as T : undefined;
|
||||
return item ?? initialValue;
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user