mirror of
https://github.com/duckietm/Nitro-V3.git
synced 2026-06-20 07:26:19 +00:00
chore: checkpoint current work
This commit is contained in:
@@ -256,7 +256,7 @@ const CatalogModernViewInner: FC<{}> = () =>
|
||||
{ /* Content area */ }
|
||||
<div className="flex flex-1 overflow-hidden">
|
||||
{ showFavorites
|
||||
? <div className="flex-1 overflow-auto bg-card-content-area">
|
||||
? <div className="flex-1 overflow-auto nitro-card-content-shell">
|
||||
<CatalogFavoritesView onClose={ () => setShowFavorites(false) } />
|
||||
</div>
|
||||
: <>
|
||||
@@ -264,7 +264,7 @@ const CatalogModernViewInner: FC<{}> = () =>
|
||||
<div className="w-[170px] min-w-[170px] border-r-2 border-card-grid-item-border bg-card-grid-item overflow-y-auto py-1">
|
||||
<CatalogNavigationView node={ activeNodes[0] } />
|
||||
</div> }
|
||||
<div className="flex-1 overflow-auto p-2 bg-card-content-area">
|
||||
<div className="flex-1 overflow-auto p-2 nitro-card-content-shell">
|
||||
{ adminMode && <CatalogAdminPageEditView /> }
|
||||
{ GetCatalogLayout(currentPage, () => setNavigationHidden(true)) }
|
||||
</div>
|
||||
|
||||
@@ -110,9 +110,9 @@ export const CatalogAdminOfferEditView: FC<{}> = () =>
|
||||
<div className="fixed inset-0 flex items-center justify-center" style={ { zIndex: 1000 } } onClick={ () => setEditingOffer(null) }>
|
||||
<div className="absolute inset-0 bg-black/30 backdrop-blur-[1px]" />
|
||||
|
||||
<div className="relative bg-light rounded-lg w-[420px] border-2 border-card-border overflow-hidden shadow-lg" onClick={ e => e.stopPropagation() }>
|
||||
<div className="nitro-card-shell relative w-[420px] overflow-hidden shadow-lg" onClick={ e => e.stopPropagation() }>
|
||||
{ /* Header */ }
|
||||
<div className="flex items-center justify-between px-3 py-2 bg-card-header">
|
||||
<div className="nitro-card-header-shell flex items-center justify-between px-3 py-2">
|
||||
<span className="text-sm font-bold text-white">
|
||||
{ isNew ? LocalizeText('catalog.admin.offer.new') : `${ LocalizeText('catalog.admin.offer.edit') } #${ editingOffer.offerId }` }
|
||||
</span>
|
||||
|
||||
@@ -1,19 +1,50 @@
|
||||
import { FC } from 'react';
|
||||
import { FC, useEffect, useState } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { useFriends } from '../../hooks';
|
||||
import { FriendBarView } from './views/friends-bar/FriendsBarView';
|
||||
import { FriendsListView } from './views/friends-list/FriendsListView';
|
||||
import { FriendsMessengerView } from './views/messenger/FriendsMessengerView';
|
||||
|
||||
export const FriendsView: FC<{}> = props =>
|
||||
{
|
||||
const { settings = null, onlineFriends = [] } = useFriends();
|
||||
const FRIEND_BAR_TARGET_IDS = [ 'toolbar-friend-bar-container-desktop' ];
|
||||
|
||||
export const FriendsView: FC<{}> = props => {
|
||||
const { settings = null, onlineFriends = [], requests = [] } = useFriends();
|
||||
const [ portalTarget, setPortalTarget ] = useState<HTMLElement | null>(null);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(typeof document === 'undefined') return;
|
||||
|
||||
const resolveTarget = () =>
|
||||
{
|
||||
for(const id of FRIEND_BAR_TARGET_IDS)
|
||||
{
|
||||
const element = document.getElementById(id);
|
||||
|
||||
if(element)
|
||||
{
|
||||
setPortalTarget(previous => ((previous === element) ? previous : element));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
setPortalTarget(null);
|
||||
};
|
||||
|
||||
resolveTarget();
|
||||
|
||||
const observer = new MutationObserver(resolveTarget);
|
||||
|
||||
observer.observe(document.body, { childList: true, subtree: true });
|
||||
|
||||
return () => observer.disconnect();
|
||||
}, []);
|
||||
|
||||
if(!settings) return null;
|
||||
|
||||
return (
|
||||
<>
|
||||
{ createPortal(<FriendBarView onlineFriends={ onlineFriends } />, document.getElementById('toolbar-friend-bar-container')) }
|
||||
{ portalTarget && createPortal(<FriendBarView onlineFriends={ onlineFriends } requestsCount={ requests.length } />, portalTarget) }
|
||||
<FriendsListView />
|
||||
<FriendsMessengerView />
|
||||
</>
|
||||
|
||||
@@ -1,66 +1,108 @@
|
||||
import { FindNewFriendsMessageComposer, MouseEventType } from '@nitrots/nitro-renderer';
|
||||
import { FC, useEffect, useRef, useState } from 'react';
|
||||
import { GetUserProfile, LocalizeText, MessengerFriend, OpenMessengerChat, SendMessageComposer } from '../../../../api';
|
||||
import { Button, LayoutAvatarImageView, LayoutBadgeImageView } from '../../../../common';
|
||||
import { LayoutAvatarImageView, LayoutBadgeImageView } from '../../../../common';
|
||||
import { useFriends } from '../../../../hooks';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
|
||||
export const FriendBarItemView: FC<{ friend: MessengerFriend }> = props =>
|
||||
{
|
||||
export const FriendBarItemView: FC<{ friend: MessengerFriend }> = props => {
|
||||
const { friend = null } = props;
|
||||
const [ isVisible, setVisible ] = useState(false);
|
||||
const [isVisible, setVisible] = useState(false);
|
||||
const { followFriend = null } = useFriends();
|
||||
const elementRef = useRef<HTMLDivElement>();
|
||||
const elementRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
const onClick = (event: MouseEvent) =>
|
||||
{
|
||||
useEffect(() => {
|
||||
const onClick = (event: MouseEvent) => {
|
||||
const element = elementRef.current;
|
||||
|
||||
if(!element) return;
|
||||
|
||||
if((event.target !== element) && !element.contains((event.target as Node)))
|
||||
{
|
||||
if (!element) return;
|
||||
if ((event.target !== element) && !element.contains((event.target as Node))) {
|
||||
setVisible(false);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener(MouseEventType.MOUSE_CLICK, onClick);
|
||||
|
||||
return () => document.removeEventListener(MouseEventType.MOUSE_CLICK, onClick);
|
||||
}, []);
|
||||
|
||||
if(!friend)
|
||||
{
|
||||
if (!friend) {
|
||||
return (
|
||||
<div ref={ elementRef } className={ 'friend-bar-item btn btn-secondary w-[130px] mx-[3px] my-0 z-0 relative pl-[37px] text-left friend-bar-search ' + (isVisible ? 'friend-bar-search-item-active' : '') } onClick={ () => setVisible(prev => !prev) }>
|
||||
<div className="friend-bar-item-head absolute -top-[3px] left-[5px] w-[31px] h-[34px] bg-[url('@/assets/images/toolbar/friend-search.png')]" />
|
||||
<div className="truncate text-white text-[13px]">{ LocalizeText('friend.bar.find.title') }</div>
|
||||
{ isVisible &&
|
||||
<div className="search-content mt-3">
|
||||
<div className="bg-white text-black px-1 py-1 text-xs">{ LocalizeText('friend.bar.find.text') }</div>
|
||||
<Button className="mt-2 mb-4" variant="secondary" onClick={ () => SendMessageComposer(new FindNewFriendsMessageComposer()) }>{ LocalizeText('friend.bar.find.button') }</Button>
|
||||
</div> }
|
||||
<div ref={elementRef} className="relative">
|
||||
<motion.button
|
||||
type="button"
|
||||
whileHover={{ scale: 1.02 }} whileTap={{ scale: 0.98 }}
|
||||
className="relative flex h-[34px] w-[132px] items-center rounded-[7px] border border-[#9fc56f] bg-[#5f7d2f] pl-[34px] pr-[10px] text-left text-white shadow-[inset_0_1px_0_rgba(255,255,255,0.18),0_2px_0_rgba(0,0,0,0.25)]"
|
||||
onClick={() => setVisible(prev => !prev)}
|
||||
>
|
||||
<div className="absolute left-[6px] top-1/2 h-[24px] w-[24px] -translate-y-1/2 bg-[url('@/assets/images/toolbar/friend-search.png')] bg-contain bg-center bg-no-repeat pointer-events-none" />
|
||||
<div className="truncate text-[0.8rem] font-bold">{LocalizeText('friend.bar.find.title')}</div>
|
||||
</motion.button>
|
||||
|
||||
<AnimatePresence>
|
||||
{isVisible && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 10, scale: 0.95 }}
|
||||
animate={{ opacity: 1, y: 0, scale: 1 }}
|
||||
exit={{ opacity: 0, y: 10, scale: 0.95 }}
|
||||
transition={{ type: "spring", stiffness: 400, damping: 25 }}
|
||||
className="absolute bottom-[calc(100%+12px)] left-1/2 -translate-x-1/2 tbme-panel whitespace-nowrap z-[80] flex flex-col items-center gap-2 pointer-events-auto min-w-[170px]"
|
||||
>
|
||||
<div className="text-white text-[13px] font-bold drop-shadow-[1px_1px_0_#000]">{LocalizeText('friend.bar.find.title')}</div>
|
||||
<div className="text-white/80 text-xs px-2">{LocalizeText('friend.bar.find.text')}</div>
|
||||
<button
|
||||
className="px-3 py-1 bg-black/40 hover:bg-black/60 border border-white/10 rounded-lg text-white text-[11px] font-bold transition-colors cursor-pointer mt-1"
|
||||
onClick={event => { event.stopPropagation(); SendMessageComposer(new FindNewFriendsMessageComposer()); setVisible(false); }}
|
||||
>
|
||||
{LocalizeText('friend.bar.find.button')}
|
||||
</button>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div ref={ elementRef } className={ `friend-bar-item btn btn-success ${ isVisible ? 'w-[165px]' : 'w-[130px]' } mx-[3px] my-0 z-0 relative text-left${ isVisible ? ' mb-[84px]' : '' }` } style={{ paddingLeft: friend.id > 0 ? '70px' : '46px', paddingRight: isVisible ? '4px' : undefined }} onClick={ () => setVisible(prev => !prev) }>
|
||||
<div className={ `friend-bar-item-head absolute ${ friend.id > 0 ? '-top-[31px] -left-[25px]' : '-top-[5px] -left-[3.5px]' }` }>
|
||||
{ (friend.id > 0) &&
|
||||
<LayoutAvatarImageView direction={ isVisible ? 2 : 3 } figure={ friend.figure } headOnly={ !isVisible } /> }
|
||||
{ (friend.id <= 0) &&
|
||||
<LayoutBadgeImageView badgeCode="ADM" isGroup={ false } /> }
|
||||
<div ref={elementRef} className="relative">
|
||||
<div className="absolute left-[-4px] bottom-[-2px] z-10 h-[66px] w-[34px] overflow-hidden pointer-events-none">
|
||||
{(friend.id > 0) ? (
|
||||
<LayoutAvatarImageView
|
||||
direction={2}
|
||||
figure={friend.figure}
|
||||
headOnly={false}
|
||||
className="block pointer-events-none drop-shadow-[1px_1px_0_rgba(0,0,0,0.6)]"
|
||||
style={ { marginLeft: '-28px', marginTop: '-10px' } }
|
||||
/>
|
||||
) : (
|
||||
<LayoutBadgeImageView badgeCode="ADM" isGroup={false} className="scale-75 block pointer-events-none drop-shadow-[1px_1px_0_rgba(0,0,0,0.6)]" />
|
||||
)}
|
||||
</div>
|
||||
<div className="truncate text-white text-[13px]">{ friend.name }</div>
|
||||
{ isVisible &&
|
||||
<div className="flex justify-between gap-2 mt-1">
|
||||
<div className="cursor-pointer nitro-friends-spritesheet icon-friendbar-chat" onClick={ event => { event.stopPropagation(); OpenMessengerChat(friend.id); } } />
|
||||
{ friend.followingAllowed &&
|
||||
<div className="cursor-pointer nitro-friends-spritesheet icon-friendbar-visit" onClick={ event => { event.stopPropagation(); followFriend(friend); } } /> }
|
||||
<div className="cursor-pointer nitro-friends-spritesheet icon-profile" onClick={ event => { event.stopPropagation(); GetUserProfile(friend.id); } } />
|
||||
</div> }
|
||||
<motion.button
|
||||
type="button"
|
||||
whileHover={{ scale: 1.02 }} whileTap={{ scale: 0.98 }}
|
||||
className="relative flex h-[34px] w-[132px] items-center rounded-[7px] border border-[#9fc56f] bg-[#6f8f39] pl-[44px] pr-[10px] text-left text-white shadow-[inset_0_1px_0_rgba(255,255,255,0.18),0_2px_0_rgba(0,0,0,0.25)] overflow-visible"
|
||||
onClick={() => setVisible(prev => !prev)}
|
||||
>
|
||||
<div className="truncate text-[0.82rem] font-bold">{friend.name}</div>
|
||||
</motion.button>
|
||||
|
||||
<AnimatePresence>
|
||||
{isVisible && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 10, scale: 0.95 }}
|
||||
animate={{ opacity: 1, y: 0, scale: 1 }}
|
||||
exit={{ opacity: 0, y: 10, scale: 0.95 }}
|
||||
transition={{ type: "spring", stiffness: 400, damping: 25 }}
|
||||
className="absolute bottom-[calc(100%+12px)] left-1/2 -translate-x-1/2 tbme-panel flex flex-col items-center gap-2 z-[80] pointer-events-auto min-w-[110px]"
|
||||
>
|
||||
<div className="text-white font-bold text-[13px] drop-shadow-[1px_1px_0_#000] truncate max-w-[120px] px-1">{friend.name}</div>
|
||||
<div className="flex justify-center gap-3 px-2">
|
||||
<div className="cursor-pointer tbme-icon nitro-friends-spritesheet icon-friendbar-chat hover:-translate-y-1 transition-transform" onClick={event => { event.stopPropagation(); OpenMessengerChat(friend.id); setVisible(false); }} />
|
||||
{friend.followingAllowed &&
|
||||
<div className="cursor-pointer tbme-icon nitro-friends-spritesheet icon-friendbar-visit hover:-translate-y-1 transition-transform" onClick={event => { event.stopPropagation(); followFriend(friend); setVisible(false); }} />}
|
||||
<div className="cursor-pointer tbme-icon nitro-friends-spritesheet icon-profile hover:-translate-y-1 transition-transform" onClick={event => { event.stopPropagation(); GetUserProfile(friend.id); setVisible(false); }} />
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,26 +1,133 @@
|
||||
import { FC, useRef, useState } from 'react';
|
||||
import { FaChevronLeft, FaChevronRight } from 'react-icons/fa';
|
||||
import { MessengerFriend } from '../../../../api';
|
||||
import { Button } from '../../../../common';
|
||||
import { LocalizeText, MessengerFriend } from '../../../../api';
|
||||
import { FriendBarItemView } from './FriendBarItemView';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
|
||||
const MAX_DISPLAY_COUNT = 3;
|
||||
|
||||
export const FriendBarView: FC<{ onlineFriends: MessengerFriend[] }> = props =>
|
||||
{
|
||||
const { onlineFriends = null } = props;
|
||||
// Mirrored from Toolbar to keep physics identical
|
||||
const containerVariants = {
|
||||
hidden: {},
|
||||
visible: { transition: { staggerChildren: 0.05 } },
|
||||
exit: { transition: { staggerChildren: 0.03, staggerDirection: -1 as const } },
|
||||
};
|
||||
|
||||
const itemVariants = {
|
||||
hidden: { opacity: 0, y: 10, scale: 0.8 },
|
||||
visible: { opacity: 1, y: 0, scale: 1, transition: { type: 'spring', stiffness: 400, damping: 22 } },
|
||||
exit: { opacity: 0, y: 6, scale: 0.85, transition: { duration: 0.1 } },
|
||||
};
|
||||
|
||||
export const FriendBarView: FC<{ onlineFriends: MessengerFriend[]; requestsCount?: number }> = props => {
|
||||
const { onlineFriends = [], requestsCount = 0 } = props;
|
||||
const [ indexOffset, setIndexOffset ] = useState(0);
|
||||
const elementRef = useRef<HTMLDivElement>();
|
||||
const elementRef = useRef<HTMLDivElement>(null);
|
||||
const hasScrollableFriends = (onlineFriends.length > MAX_DISPLAY_COUNT);
|
||||
const visibleFriends = onlineFriends.slice(indexOffset, (indexOffset + MAX_DISPLAY_COUNT));
|
||||
|
||||
return (
|
||||
<div ref={ elementRef } className="flex items-center ">
|
||||
<Button className="z-2 cursor-pointer" disabled={ (indexOffset <= 0) } variant="black" onClick={ event => setIndexOffset(indexOffset - 1) }>
|
||||
<FaChevronLeft className="fa-icon" />
|
||||
</Button>
|
||||
{ Array.from(Array(MAX_DISPLAY_COUNT), (e, i) => <FriendBarItemView key={ i } friend={ (onlineFriends[indexOffset + i] || null) } />) }
|
||||
<Button className="z-2 cursor-pointer" disabled={ !((onlineFriends.length > MAX_DISPLAY_COUNT) && ((indexOffset + MAX_DISPLAY_COUNT) <= (onlineFriends.length - 1))) } variant="black" onClick={ event => setIndexOffset(indexOffset + 1) }>
|
||||
<FaChevronRight className="fa-icon" />
|
||||
</Button>
|
||||
</div>
|
||||
<motion.div
|
||||
ref={elementRef}
|
||||
className="flex h-[40px] items-center gap-[6px] px-[2px] py-[3px]"
|
||||
variants={containerVariants}
|
||||
initial="hidden"
|
||||
animate="visible"
|
||||
exit="exit"
|
||||
>
|
||||
{ (requestsCount > 0) &&
|
||||
<motion.div variants={ itemVariants }>
|
||||
<div className="flex h-[34px] items-center rounded-[7px] border border-[#9fc56f] bg-[#5f7d2f] px-[10px] text-[0.74rem] font-bold whitespace-nowrap text-white shadow-[inset_0_1px_0_rgba(255,255,255,0.18),0_2px_0_rgba(0,0,0,0.25)]">
|
||||
{ requestsCount } richieste
|
||||
</div>
|
||||
</motion.div> }
|
||||
<motion.div variants={itemVariants}>
|
||||
<div
|
||||
className={ `flex h-[34px] w-[20px] items-center justify-center text-white/80 transition-all ${ (!hasScrollableFriends || (indexOffset <= 0)) ? 'opacity-30 cursor-not-allowed' : 'cursor-pointer hover:text-white active:scale-95' }` }
|
||||
onClick={ () => { if(indexOffset > 0) setIndexOffset(indexOffset - 1); } }
|
||||
>
|
||||
<FaChevronLeft className="text-white/70 text-sm drop-shadow-[1px_1px_0_#000]" />
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
<AnimatePresence mode="popLayout">
|
||||
{ visibleFriends.map(friend => (
|
||||
<motion.div
|
||||
key={ friend.id }
|
||||
variants={ itemVariants }
|
||||
layout
|
||||
initial="hidden"
|
||||
animate="visible"
|
||||
exit="exit"
|
||||
>
|
||||
<FriendBarItemView friend={ friend } />
|
||||
</motion.div>
|
||||
)) }
|
||||
<motion.div
|
||||
key="friend-search"
|
||||
variants={ itemVariants }
|
||||
layout
|
||||
initial="hidden"
|
||||
animate="visible"
|
||||
exit="exit"
|
||||
>
|
||||
<FriendBarItemView friend={ null } />
|
||||
</motion.div>
|
||||
{ (!onlineFriends.length && (requestsCount <= 0)) &&
|
||||
<motion.div
|
||||
key="friend-empty"
|
||||
variants={ itemVariants }
|
||||
layout
|
||||
initial="hidden"
|
||||
animate="visible"
|
||||
exit="exit"
|
||||
>
|
||||
<div className="flex h-[34px] items-center rounded-[7px] border border-[#9fc56f] bg-[#5f7d2f] px-[10px] text-[0.74rem] font-medium whitespace-nowrap text-white shadow-[inset_0_1px_0_rgba(255,255,255,0.18),0_2px_0_rgba(0,0,0,0.25)]">
|
||||
Nessun amico online
|
||||
</div>
|
||||
</motion.div> }
|
||||
</AnimatePresence>
|
||||
|
||||
<motion.div variants={itemVariants}>
|
||||
<div
|
||||
className={ `flex h-[34px] w-[20px] items-center justify-center text-white/80 transition-all ${ (!hasScrollableFriends || !((onlineFriends.length > MAX_DISPLAY_COUNT) && ((indexOffset + MAX_DISPLAY_COUNT) <= (onlineFriends.length - 1)))) ? 'opacity-30 cursor-not-allowed' : 'cursor-pointer hover:text-white active:scale-95' }` }
|
||||
onClick={ () => { if((onlineFriends.length > MAX_DISPLAY_COUNT) && ((indexOffset + MAX_DISPLAY_COUNT) <= (onlineFriends.length - 1))) setIndexOffset(indexOffset + 1); } }
|
||||
>
|
||||
<FaChevronRight className="text-white/70 text-sm drop-shadow-[1px_1px_0_#000]" />
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
<style>{FRIENDBAR_STYLES}</style>
|
||||
</motion.div>
|
||||
);
|
||||
};
|
||||
|
||||
const FRIENDBAR_STYLES = `
|
||||
.tbme-panel {
|
||||
background: rgba(18, 16, 14, 0.88);
|
||||
backdrop-filter: blur(20px);
|
||||
-webkit-backdrop-filter: blur(20px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
border-radius: 14px;
|
||||
padding: 10px 12px;
|
||||
box-shadow:
|
||||
0 10px 36px rgba(0, 0, 0, 0.65),
|
||||
0 1px 0 rgba(255, 255, 255, 0.05) inset;
|
||||
}
|
||||
|
||||
.tbme-icon {
|
||||
opacity: 0.72;
|
||||
cursor: pointer;
|
||||
transition: opacity 0.15s ease, transform 0.15s ease;
|
||||
}
|
||||
|
||||
.tbme-icon:hover {
|
||||
opacity: 1;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.tbme-icon:active {
|
||||
opacity: 0.85;
|
||||
transform: translateY(0);
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { DesktopViewEvent, GetGuestRoomResultEvent, GetSessionDataManager, GroupInformationComposer, GroupInformationEvent, GroupInformationParser, GroupRemoveMemberComposer, HabboGroupDeactivatedMessageEvent, RoomEntryInfoMessageEvent } from '@nitrots/nitro-renderer';
|
||||
import { FC, useEffect, useRef, useState } from 'react';
|
||||
import { FaChevronDown, FaChevronUp } from 'react-icons/fa';
|
||||
import { FaChevronDown } from 'react-icons/fa';
|
||||
import { GetGroupInformation, GetGroupManager, GroupMembershipType, GroupType, LocalizeText, SendMessageComposer, TryJoinGroup } from '../../../api';
|
||||
import groupIcon from '../../../assets/images/rightside/group.png';
|
||||
import { Button, Flex, LayoutBadgeImageView, Text } from '../../../common';
|
||||
import { useMessageEvent, useNotification } from '../../../hooks';
|
||||
|
||||
@@ -12,6 +13,7 @@ export const GroupRoomInformationView: FC<{}> = props =>
|
||||
const requestRetryTimeoutRef = useRef<ReturnType<typeof setTimeout>>(null);
|
||||
const [ groupInformation, setGroupInformation ] = useState<GroupInformationParser>(null);
|
||||
const [ isOpen, setIsOpen ] = useState<boolean>(true);
|
||||
const [ isCompact, setIsCompact ] = useState(false);
|
||||
const { showConfirm = null } = useNotification();
|
||||
|
||||
const clearRequestRetryTimeout = () =>
|
||||
@@ -113,6 +115,19 @@ export const GroupRoomInformationView: FC<{}> = props =>
|
||||
|
||||
useEffect(() => () => clearRequestRetryTimeout(), []);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(isOpen)
|
||||
{
|
||||
setIsCompact(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const timeout = window.setTimeout(() => setIsCompact(true), 220);
|
||||
|
||||
return () => window.clearTimeout(timeout);
|
||||
}, [ isOpen ]);
|
||||
|
||||
const leaveGroup = () =>
|
||||
{
|
||||
showConfirm(LocalizeText('group.leaveconfirm.desc'), () =>
|
||||
@@ -157,27 +172,43 @@ export const GroupRoomInformationView: FC<{}> = props =>
|
||||
if(!groupInformation) return null;
|
||||
|
||||
return (
|
||||
<div className="pointer-events-auto px-[5px] py-[6px] [box-shadow:inset_0_5px_#22222799,inset_0_-4px_#12121599] bg-[#1c1c20f2] rounded text-sm">
|
||||
<div className="flex flex-col gap-2">
|
||||
<Flex pointer alignItems="center" justifyContent="between" onClick={ event => setIsOpen(value => !value) }>
|
||||
<Text variant="white">{ LocalizeText('group.homeroominfo.title') }</Text>
|
||||
{ isOpen && <FaChevronUp className="fa-icon" /> }
|
||||
{ !isOpen && <FaChevronDown className="fa-icon" /> }
|
||||
<div className={ `pointer-events-auto mt-[6px] overflow-hidden rounded-[10px] border border-white/6 bg-[rgba(10,10,12,0.58)] text-sm shadow-[0_8px_18px_rgba(0,0,0,0.12)] transition-[width,max-width] duration-200 ease-out ${ isCompact ? 'ml-auto w-[52px] max-w-[52px]' : 'w-full max-w-[188px]' }` }>
|
||||
<div className="flex flex-col">
|
||||
<Flex pointer alignItems="center" justifyContent={ isCompact ? 'end' : 'between' } className={ `border-b border-white/6 bg-[linear-gradient(180deg,rgba(255,255,255,0.08),rgba(255,255,255,0.03))] px-[7px] py-[5px] max-[420px]:px-[6px] max-[420px]:py-[4px] ${ isCompact ? 'gap-[5px] px-[6px] py-[5px]' : '' }` } onClick={ event => setIsOpen(value => !value) }>
|
||||
<Flex alignItems="center" gap={ 1 } className={ isCompact ? 'mr-[0]' : '' }>
|
||||
<div className="flex h-[18px] w-[18px] items-center justify-center">
|
||||
<img src={ groupIcon } alt="" className="h-[14px] w-auto object-contain" />
|
||||
</div>
|
||||
{ !isCompact && <Text variant="white" className="text-[0.78rem] font-bold leading-none text-white/90 max-[420px]:text-[0.74rem]">{ LocalizeText('group.homeroominfo.title') }</Text> }
|
||||
</Flex>
|
||||
<div className={ `flex h-[20px] w-[20px] items-center justify-center rounded-[6px] border border-white/8 bg-white/6 text-white/80 transition-transform duration-300 ease-out ${ isOpen ? 'rotate-180' : 'rotate-0' }` }>
|
||||
<FaChevronDown className="fa-icon text-[10px]" />
|
||||
</div>
|
||||
</Flex>
|
||||
{ isOpen &&
|
||||
<div className={ `overflow-hidden transition-all duration-[580ms] ease-[cubic-bezier(0.16,1,0.3,1)] ${ isOpen ? 'max-h-[280px] opacity-100 translate-y-0 scale-y-100' : 'max-h-0 opacity-0 -translate-y-[8px] scale-y-95' } origin-top` }>
|
||||
<>
|
||||
<Flex pointer alignItems="center" gap={ 2 } onClick={ event => GetGroupInformation(groupInformation.id) }>
|
||||
<div className="group-badge">
|
||||
<LayoutBadgeImageView badgeCode={ groupInformation.badge } isGroup={ true } />
|
||||
<Flex pointer alignItems="center" gap={ 2 } className={ `px-[10px] py-[10px] max-[420px]:px-[8px] max-[420px]:py-[8px] transition-all duration-[480ms] ease-[cubic-bezier(0.16,1,0.3,1)] ${ isOpen ? 'translate-y-0 opacity-100 delay-[80ms]' : '-translate-y-[6px] opacity-0 delay-0' }` } onClick={ event => GetGroupInformation(groupInformation.id) }>
|
||||
<div className="group-badge flex h-[42px] w-[42px] shrink-0 items-center justify-center overflow-hidden max-[420px]:h-[38px] max-[420px]:w-[38px]">
|
||||
<LayoutBadgeImageView
|
||||
badgeCode={ groupInformation.badge }
|
||||
isGroup={ true }
|
||||
classNames={ [ 'w-full!', 'h-full!', 'bg-contain!' ] }
|
||||
style={ { width: '100%', height: '100%', backgroundSize: 'contain' } }
|
||||
/>
|
||||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
<Text truncate variant="white" className="text-[0.82rem] font-bold leading-tight text-white/92 max-[420px]:text-[0.76rem]">{ groupInformation.title }</Text>
|
||||
</div>
|
||||
<Text variant="white">{ groupInformation.title }</Text>
|
||||
</Flex>
|
||||
{ (groupInformation.type !== GroupType.PRIVATE || isRealOwner) &&
|
||||
<Button fullWidth disabled={ (groupInformation.membershipType === GroupMembershipType.REQUEST_PENDING) } variant="success" onClick={ handleButtonClick }>
|
||||
{ LocalizeText(getButtonText()) }
|
||||
</Button>
|
||||
<div className={ `px-[9px] pb-[9px] max-[420px]:px-[8px] max-[420px]:pb-[8px] transition-all duration-[480ms] ease-[cubic-bezier(0.16,1,0.3,1)] ${ isOpen ? 'translate-y-0 opacity-100 delay-[160ms]' : '-translate-y-[6px] opacity-0 delay-0' }` }>
|
||||
<Button fullWidth disabled={ (groupInformation.membershipType === GroupMembershipType.REQUEST_PENDING) } className="h-[30px] rounded-[6px] border border-white/10 bg-[linear-gradient(180deg,rgba(255,255,255,0.88),rgba(214,214,214,0.76))] !text-black text-[0.82rem] font-bold shadow-[inset_0_1px_0_rgba(255,255,255,0.75),0_1px_0_rgba(0,0,0,0.18)] hover:brightness-100 max-[420px]:h-[28px] max-[420px]:text-[0.78rem]" onClick={ handleButtonClick }>
|
||||
{ LocalizeText(getButtonText()) }
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
</> }
|
||||
</>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -17,7 +17,7 @@ export const GuideToolAcceptView: FC<GuideToolAcceptViewProps> = props =>
|
||||
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-col gap-0 bg-muted p-2 rounded">
|
||||
<div className="nitro-card-panel flex flex-col gap-0 p-2">
|
||||
<Text bold>{ LocalizeText('guide.help.request.guide.accept.request.title') }</Text>
|
||||
<Text variant="muted">{ LocalizeText('guide.help.request.type.1') }</Text>
|
||||
<Text textBreak wrap>{ helpRequestDescription }</Text>
|
||||
|
||||
@@ -35,7 +35,7 @@ export const GuideToolMenuView: FC<GuideToolMenuViewProps> = props =>
|
||||
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
<Flex alignItems="center" className="bg-muted p-2 rounded" gap={ 2 }>
|
||||
<Flex alignItems="center" className="nitro-card-panel p-2" gap={ 2 }>
|
||||
<div className={ 'duty-switch' + (isOnDuty ? '' : ' off') } onClick={ event => processAction('toggle_duty') } />
|
||||
<Column gap={ 0 }>
|
||||
<Text bold>{ LocalizeText('guide.help.guide.tool.yourstatus') }</Text>
|
||||
@@ -57,7 +57,7 @@ export const GuideToolMenuView: FC<GuideToolMenuViewProps> = props =>
|
||||
<Text>{ LocalizeText('guide.help.guide.tool.tickettypeselection.bullyreports') }</Text>
|
||||
</div>
|
||||
</div>
|
||||
<hr className="bg-dark m-0" />
|
||||
<hr className="nitro-card-divider m-0" />
|
||||
<div className="flex justify-enter items-center gap-2">
|
||||
<div className="info-icon" />
|
||||
<div className="flex flex-col gap-1">
|
||||
@@ -66,7 +66,7 @@ export const GuideToolMenuView: FC<GuideToolMenuViewProps> = props =>
|
||||
<div dangerouslySetInnerHTML={ { __html: LocalizeText('guide.help.guide.tool.guardiansonduty', [ 'amount' ], [ guardiansOnDuty.toString() ]) } } />
|
||||
</div>
|
||||
</div>
|
||||
<hr className="bg-dark m-0" />
|
||||
<hr className="nitro-card-divider m-0" />
|
||||
<Flex gap={ 2 } justifyContent="between">
|
||||
<Button disabled onClick={ event => processAction('forum_link') }>{ LocalizeText('guide.help.guide.tool.forum.link') }</Button>
|
||||
<Button disabled>{ LocalizeText('guide.help.guide.tool.skill.link') }</Button>
|
||||
|
||||
@@ -73,7 +73,7 @@ export const GuideToolOngoingView: FC<GuideToolOngoingViewProps> = props =>
|
||||
|
||||
return (
|
||||
<Column fullHeight>
|
||||
<Flex alignItems="center" className="p-2 rounded bg-muted" gap={ 1 } justifyContent="between">
|
||||
<Flex alignItems="center" className="nitro-card-panel p-2" gap={ 1 } justifyContent="between">
|
||||
{ isGuide &&
|
||||
<div className="relative inline-flex align-middle">
|
||||
<Button onClick={ visit }>{ LocalizeText('guide.help.request.guide.ongoing.visit.button') }</Button>
|
||||
@@ -86,7 +86,7 @@ export const GuideToolOngoingView: FC<GuideToolOngoingViewProps> = props =>
|
||||
</Column> }
|
||||
<Button disabled variant="danger">{ LocalizeText('guide.help.common.report.link') }</Button>
|
||||
</Flex>
|
||||
<Column className="p-2 rounded bg-muted chat-messages" gap={ 1 } overflow="hidden">
|
||||
<Column className="nitro-card-panel p-2 chat-messages" gap={ 1 } overflow="hidden">
|
||||
<Column overflow="auto">
|
||||
{ messageGroups.map((group, index) =>
|
||||
{
|
||||
@@ -96,7 +96,7 @@ export const GuideToolOngoingView: FC<GuideToolOngoingViewProps> = props =>
|
||||
{ (!isOwnChat(group.userId)) &&
|
||||
<LayoutAvatarImageView direction={ 2 } figure={ userFigure } /> }
|
||||
</div>
|
||||
<div className={ 'bg-light text-black border-radius mb-2 rounded py-1 px-2 messages-group-' + (isOwnChat(group.userId) ? 'right' : 'left') }>
|
||||
<div className={ 'nitro-card-row text-black border-radius mb-2 py-1 px-2 messages-group-' + (isOwnChat(group.userId) ? 'right' : 'left') }>
|
||||
<Text bold>
|
||||
{ (isOwnChat(group.userId)) && GetSessionDataManager().userName }
|
||||
{ (!isOwnChat(group.userId)) && userName }
|
||||
|
||||
@@ -16,7 +16,7 @@ export const GuideToolUserFeedbackView: FC<GuideToolUserFeedbackViewProps> = pro
|
||||
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
<Flex className="bg-muted p-2 rounded" gap={ 1 } justifyContent="between">
|
||||
<Flex className="nitro-card-panel p-2" gap={ 1 } justifyContent="between">
|
||||
<Column gap={ 0 }>
|
||||
<Text bold>{ userName }</Text>
|
||||
<Text>{ LocalizeText('guide.help.request.user.feedback.guide.desc') }</Text>
|
||||
@@ -29,7 +29,7 @@ export const GuideToolUserFeedbackView: FC<GuideToolUserFeedbackViewProps> = pro
|
||||
</div>
|
||||
{ userName && (userName.length > 0) &&
|
||||
<>
|
||||
<hr className="bg-dark m-0 mt-auto" />
|
||||
<hr className="nitro-card-divider m-0 mt-auto" />
|
||||
<div className="flex flex-col">
|
||||
<Text bold center>{ LocalizeText('guide.help.request.user.feedback.question') }</Text>
|
||||
<div className="flex gap-1">
|
||||
|
||||
@@ -17,7 +17,7 @@ export const GuideToolUserPendingView: FC<GuideToolUserPendingViewProps> = props
|
||||
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
<Column className="bg-muted rounded p-2" gap={ 0 }>
|
||||
<Column className="nitro-card-panel p-2" gap={ 0 }>
|
||||
<Text bold>{ LocalizeText('guide.help.request.guide.accept.request.title') }</Text>
|
||||
<Text variant="muted">{ LocalizeText('guide.help.request.type.1') }</Text>
|
||||
<Text textBreak wrap>{ helpRequestDescription }</Text>
|
||||
|
||||
@@ -30,7 +30,7 @@ export const NameChangeConfirmationView: FC<NameChangeLayoutViewProps> = props =
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4 h-full">
|
||||
<div className="bg-muted rounded p-2 text-center">{ LocalizeText('tutorial.name_change.info.confirm') }</div>
|
||||
<div className="nitro-card-panel p-2 text-center">{ LocalizeText('tutorial.name_change.info.confirm') }</div>
|
||||
<div className="flex flex-col items-center gap-1 h-full">
|
||||
<div>{ LocalizeText('tutorial.name_change.confirm') }</div>
|
||||
<div className="font-bold ">{ username }</div>
|
||||
|
||||
@@ -9,7 +9,7 @@ export const NameChangeInitView: FC<NameChangeLayoutViewProps> = props =>
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4 h-full">
|
||||
<div className="bg-muted rounded p-2 text-center">{ LocalizeText('tutorial.name_change.info.main') }</div>
|
||||
<div className="nitro-card-panel p-2 text-center">{ LocalizeText('tutorial.name_change.info.main') }</div>
|
||||
<div className="font-bold flex items-center justify-center size-full">{ LocalizeText('tutorial.name_change.current', [ 'name' ], [ GetSessionDataManager().userName ]) }</div>
|
||||
<div className="flex gap-2">
|
||||
<button className="btn btn-success w-full" onClick={ () => onAction('start') }>{ LocalizeText('tutorial.name_change.change') }</button>
|
||||
|
||||
@@ -80,14 +80,14 @@ export const NameChangeInputView: FC<NameChangeLayoutViewProps> = props =>
|
||||
<button className="btn btn-primary" disabled={ newUsername === '' || isChecking } onClick={ check }>{ LocalizeText('tutorial.name_change.check') }</button>
|
||||
</div>
|
||||
{ !errorCode && !canProceed &&
|
||||
<div className="p-2 text-center rounded bg-muted">{ LocalizeText('help.tutorial.name.info') }</div> }
|
||||
<div className="nitro-card-panel p-2 text-center">{ LocalizeText('help.tutorial.name.info') }</div> }
|
||||
{ errorCode &&
|
||||
<div className="p-2 text-center text-white rounded bg-danger">{ LocalizeText(`help.tutorial.name.${ errorCode }`, [ 'name' ], [ newUsername ]) }</div> }
|
||||
{ canProceed &&
|
||||
<div className="p-2 text-center text-white rounded bg-success">{ LocalizeText('help.tutorial.name.available', [ 'name' ], [ newUsername ]) }</div> }
|
||||
{ suggestions &&
|
||||
<div className="flex flex-col gap-2">
|
||||
{ suggestions.map((suggestion, index) => <div key={ index } className="p-1 rounded cursor-pointer col bg-muted" onClick={ () => handleUsernameChange(suggestion) }>{ suggestion }</div>) }
|
||||
{ suggestions.map((suggestion, index) => <div key={ index } className="nitro-card-row p-1 cursor-pointer col" onClick={ () => handleUsernameChange(suggestion) }>{ suggestion }</div>) }
|
||||
</div> }
|
||||
<div className="flex gap-2">
|
||||
<button className="w-full btn btn-success" disabled={ !canProceed } onClick={ () => onAction('confirmation', newUsername) }>{ LocalizeText('tutorial.name_change.pick') }</button>
|
||||
|
||||
@@ -244,8 +244,12 @@ export const NavigatorView: FC<{}> = props =>
|
||||
<NavigatorSearchView sendSearch={ sendSearch } />
|
||||
<div ref={ elementRef } className="flex flex-col flex-1 min-h-0 overflow-auto gap-2">
|
||||
{ (searchResult && searchResult.results.map((result, index) => <NavigatorSearchResultView key={ index } searchResult={ result } />)) }
|
||||
{ (searchResult && (!searchResult.results || (searchResult.results.length === 0))) &&
|
||||
<div className="nitro-card-panel px-3 py-2 text-sm text-muted">
|
||||
{ LocalizeText(searchResult.code === 'myworld_view' ? 'navigator.no.user.rooms.to.show' : 'navigator.no.results') }
|
||||
</div> }
|
||||
</div>
|
||||
<Flex className="pt-2 border-t border-muted gap-2">
|
||||
<Flex className="nitro-card-divider pt-2 border-t gap-2">
|
||||
<Flex
|
||||
pointer
|
||||
alignItems="center"
|
||||
|
||||
@@ -163,7 +163,7 @@ export const NavigatorRoomInfoView: FC<NavigatorRoomInfoViewProps> = props =>
|
||||
{ (navigatorData.enteredGuestRoom.tags.length > 0) &&
|
||||
<div className="flex flex-wrap items-center gap-1 mt-1">
|
||||
{ navigatorData.enteredGuestRoom.tags.map(tag => (
|
||||
<Text key={ tag } pointer className="px-1 rounded bg-muted cursor-pointer text-xs" onClick={ () => processAction('navigator_search_tag', tag) }>
|
||||
<Text key={ tag } pointer className="nitro-card-row px-1 cursor-pointer text-xs" onClick={ () => processAction('navigator_search_tag', tag) }>
|
||||
#{ tag }
|
||||
</Text>
|
||||
)) }
|
||||
|
||||
@@ -52,7 +52,7 @@ export const NavigatorRoomSettingsModTabView: FC<NavigatorRoomSettingsTabViewPro
|
||||
<Grid overflow="auto">
|
||||
<Column size={ 6 }>
|
||||
<Text bold>{ LocalizeText('navigator.roomsettings.moderation.banned.users') } ({ bannedUsers.length })</Text>
|
||||
<Flex overflow="hidden" className="bg-white rounded list-container p-2">
|
||||
<Flex overflow="hidden" className="nitro-card-panel list-container p-2">
|
||||
<Column fullWidth overflow="auto" gap={ 1 }>
|
||||
{ bannedUsers && (bannedUsers.length > 0) && bannedUsers.map((user, index) =>
|
||||
{
|
||||
|
||||
@@ -116,7 +116,7 @@ export const NavigatorRoomSettingsRightsTabView: FC<NavigatorRoomSettingsTabView
|
||||
) }
|
||||
</Text>
|
||||
|
||||
<Flex overflow="hidden" className="p-2 bg-white rounded list-container">
|
||||
<Flex overflow="hidden" className="nitro-card-panel p-2 list-container">
|
||||
<Column fullWidth overflow="auto" gap={ 1 }>
|
||||
{ Array.from(filteredUsersWithRights.entries()).map(([ id, name ], index) =>
|
||||
{
|
||||
@@ -155,7 +155,7 @@ export const NavigatorRoomSettingsRightsTabView: FC<NavigatorRoomSettingsTabView
|
||||
) }
|
||||
</Text>
|
||||
|
||||
<Flex overflow="hidden" className="p-2 bg-white rounded list-container">
|
||||
<Flex overflow="hidden" className="nitro-card-panel p-2 list-container">
|
||||
<Column fullWidth overflow="auto" gap={ 1 }>
|
||||
{ friendsWithoutRights.map((friend, index) =>
|
||||
{
|
||||
|
||||
@@ -89,7 +89,7 @@ export const NavigatorSearchResultItemInfoView: FC<NavigatorSearchResultItemInfo
|
||||
|
||||
return (
|
||||
<Popover
|
||||
containerClassName="max-w-[276px] not-italic font-normal leading-normal text-left no-underline text-shadow-none normal-case tracking-[normal] [word-break:normal] [word-spacing:normal] whitespace-normal text-[.7875rem] [word-wrap:break-word] bg-[#dfdfdf] bg-clip-padding border border-[solid] border-[#283F5D] rounded-[.25rem] [box-shadow:0_2px_#00000073] z-[1070]"
|
||||
containerClassName="max-w-[276px] not-italic font-normal leading-normal text-left no-underline text-shadow-none normal-case tracking-[normal] [word-break:normal] [word-spacing:normal] whitespace-normal text-[.7875rem] [word-wrap:break-word] bg-[#f2f2eb] border border-[#000] rounded-[8px] shadow-none z-[1070]"
|
||||
content={ ({ position, childRect, popoverRect }) => (
|
||||
<ArrowContainer
|
||||
arrowColor="black"
|
||||
@@ -99,7 +99,7 @@ export const NavigatorSearchResultItemInfoView: FC<NavigatorSearchResultItemInfo
|
||||
popoverRect={ popoverRect }
|
||||
position={ position }
|
||||
>
|
||||
<NitroCardContentView className="bg-transparent room-info image-rendering-pixelated" overflow="hidden" onClick={ e => e.stopPropagation() }>
|
||||
<NitroCardContentView className="bg-transparent room-info image-rendering-pixelated !p-0" overflow="hidden" onClick={ e => e.stopPropagation() }>
|
||||
<Flex gap={ 1 } overflow="hidden" className="p-2">
|
||||
<LayoutRoomThumbnailView className="flex flex-col items-center justify-end mb-1" customUrl={ roomData.officialRoomPicRef } roomId={ roomData.roomId }>
|
||||
{ roomData.habboGroupId > 0 && (
|
||||
|
||||
@@ -129,7 +129,7 @@ export const NavigatorSearchResultItemView: FC<NavigatorSearchResultItemViewProp
|
||||
pointer
|
||||
overflow="hidden"
|
||||
alignItems="center"
|
||||
className="navigator-item p-1 bg-light rounded-3 small mb-1 flex-col border border-muted"
|
||||
className="navigator-item nitro-card-row p-1 small mb-1 flex-col"
|
||||
gap={ 0 }
|
||||
onClick={ visitRoom }
|
||||
onMouseEnter={ handleMouseEnter }
|
||||
|
||||
@@ -52,14 +52,14 @@ export const NavigatorSearchResultView: FC<NavigatorSearchResultViewProps> = pro
|
||||
{
|
||||
if(!searchResult) return;
|
||||
|
||||
setIsExtended(!searchResult.closed);
|
||||
setIsExtended((searchResult.code === 'myworld_view') ? true : !searchResult.closed);
|
||||
setDisplayMode(searchResult.mode);
|
||||
}, [ searchResult ]);
|
||||
|
||||
const gridHasTwoColumns = (displayMode >= NavigatorSearchResultViewDisplayMode.THUMBNAILS);
|
||||
|
||||
return (
|
||||
<Column className="bg-white rounded border border-muted" gap={ 0 }>
|
||||
<Column className="nitro-card-panel" gap={ 0 }>
|
||||
<Flex fullWidth alignItems="center" className="px-2 py-1" justifyContent="between">
|
||||
<Flex grow pointer alignItems="center" gap={ 1 } onClick={ event => setIsExtended(prevValue => !prevValue) }>
|
||||
{ isExtended && <FaMinus className="text-secondary fa-icon" /> }
|
||||
@@ -105,6 +105,10 @@ export const NavigatorSearchResultView: FC<NavigatorSearchResultViewProps> = pro
|
||||
/>
|
||||
)) }
|
||||
</Grid> }
|
||||
{ (searchResult.rooms.length === 0) &&
|
||||
<Text className="px-3 py-2 text-sm" variant="muted">
|
||||
{ LocalizeText(searchResult.code === 'myworld_view' ? 'navigator.no.user.rooms.to.show' : 'navigator.no.results') }
|
||||
</Text> }
|
||||
</> }
|
||||
</Column>
|
||||
);
|
||||
|
||||
@@ -56,6 +56,7 @@ export interface INitroPluginApi
|
||||
// Internal plugin storage
|
||||
const _plugins: INitroPlugin[] = [];
|
||||
const _listeners: Array<() => void> = [];
|
||||
const _windowCleanup = new Map<string, () => void>();
|
||||
|
||||
function notifyListeners()
|
||||
{
|
||||
@@ -182,7 +183,7 @@ const pluginApi: INitroPluginApi = {
|
||||
let isDragging = false;
|
||||
let offsetX = 0, offsetY = 0;
|
||||
|
||||
header.addEventListener('mousedown', (e: MouseEvent) =>
|
||||
const onMouseDown = (e: MouseEvent) =>
|
||||
{
|
||||
isDragging = true;
|
||||
const rect = overlay.getBoundingClientRect();
|
||||
@@ -191,16 +192,20 @@ const pluginApi: INitroPluginApi = {
|
||||
overlay.style.transform = 'none';
|
||||
overlay.style.left = rect.left + 'px';
|
||||
overlay.style.top = rect.top + 'px';
|
||||
});
|
||||
};
|
||||
|
||||
document.addEventListener('mousemove', (e: MouseEvent) =>
|
||||
const onMouseMove = (e: MouseEvent) =>
|
||||
{
|
||||
if (!isDragging) return;
|
||||
overlay.style.left = (e.clientX - offsetX) + 'px';
|
||||
overlay.style.top = (e.clientY - offsetY) + 'px';
|
||||
});
|
||||
};
|
||||
|
||||
document.addEventListener('mouseup', () => { isDragging = false; });
|
||||
const onMouseUp = () => { isDragging = false; };
|
||||
|
||||
header.addEventListener('mousedown', onMouseDown);
|
||||
document.addEventListener('mousemove', onMouseMove);
|
||||
document.addEventListener('mouseup', onMouseUp);
|
||||
|
||||
// Content area
|
||||
const content = document.createElement('div');
|
||||
@@ -211,11 +216,23 @@ const pluginApi: INitroPluginApi = {
|
||||
overlay.appendChild(card);
|
||||
document.body.appendChild(overlay);
|
||||
|
||||
_windowCleanup.set(id, () =>
|
||||
{
|
||||
header.removeEventListener('mousedown', onMouseDown);
|
||||
document.removeEventListener('mousemove', onMouseMove);
|
||||
document.removeEventListener('mouseup', onMouseUp);
|
||||
});
|
||||
|
||||
return content;
|
||||
},
|
||||
|
||||
destroyWindow(id: string)
|
||||
{
|
||||
const cleanup = _windowCleanup.get(id);
|
||||
|
||||
cleanup?.();
|
||||
_windowCleanup.delete(id);
|
||||
|
||||
const existing = document.getElementById(`nitro-plugin-window-${id}`);
|
||||
if (existing) existing.remove();
|
||||
}
|
||||
|
||||
@@ -1,17 +1,21 @@
|
||||
import { CreateLinkEvent, HabboClubLevelEnum } from '@nitrots/nitro-renderer';
|
||||
import { FC, useMemo } from 'react';
|
||||
import { FC, useEffect, useMemo, useState } from 'react';
|
||||
import { FaChevronDown, FaQuestionCircle } from 'react-icons/fa';
|
||||
import { FriendlyTime, GetConfigurationValue, LocalizeText } from '../../api';
|
||||
import { Column, Flex, Grid, LayoutCurrencyIcon, Text } from '../../common';
|
||||
import { Column, Flex, LayoutCurrencyIcon, Text } from '../../common';
|
||||
import { usePurse } from '../../hooks';
|
||||
import purseIcon from '../../assets/images/rightside/purse.gif';
|
||||
import { CurrencyView } from './views/CurrencyView';
|
||||
import { SeasonalView } from './views/SeasonalView';
|
||||
|
||||
export const PurseView: FC<{}> = props => {
|
||||
const { purse = null, hcDisabled = false } = usePurse();
|
||||
const [ isOpen, setIsOpen ] = useState(true);
|
||||
const [ isCompact, setIsCompact ] = useState(false);
|
||||
|
||||
const displayedCurrencies = useMemo(() => GetConfigurationValue<number[]>('system.currency.types', []), []);
|
||||
const currencyDisplayNumberShort = useMemo(() => GetConfigurationValue<boolean>('currency.display.number.short', false), []);
|
||||
|
||||
|
||||
const getClubText = (() => {
|
||||
if (!purse) return null;
|
||||
|
||||
@@ -23,11 +27,10 @@ export const PurseView: FC<{}> = props => {
|
||||
else return FriendlyTime.shortFormat(totalDays * 86400);
|
||||
})();
|
||||
|
||||
const getCurrencyElements = (offset: number, limit: number = -1, seasonal: boolean = false) => {
|
||||
if (!purse || !purse.activityPoints || !purse.activityPoints.size) return null;
|
||||
const currencyTypes = useMemo(() => {
|
||||
if (!purse || !purse.activityPoints || !purse.activityPoints.size) return [];
|
||||
|
||||
const types = Array.from(purse.activityPoints.keys()).filter(type => (displayedCurrencies.indexOf(type) >= 0));
|
||||
|
||||
types.sort((a, b) => {
|
||||
if (a === 0) return -1;
|
||||
if (b === 0) return 1;
|
||||
@@ -36,60 +39,73 @@ export const PurseView: FC<{}> = props => {
|
||||
return a - b;
|
||||
});
|
||||
|
||||
let count = 0;
|
||||
return types;
|
||||
}, [ displayedCurrencies, purse ]);
|
||||
|
||||
while (count < offset) {
|
||||
types.shift();
|
||||
count++;
|
||||
const primaryCurrencies = currencyTypes.slice(0, 2);
|
||||
const seasonalCurrencies = currencyTypes.slice(2);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(isOpen)
|
||||
{
|
||||
setIsCompact(false);
|
||||
return;
|
||||
}
|
||||
|
||||
count = 0;
|
||||
const elements: JSX.Element[] = [];
|
||||
const timeout = window.setTimeout(() => setIsCompact(true), 220);
|
||||
|
||||
for (const type of types) {
|
||||
if ((limit > -1) && (count === limit)) break;
|
||||
|
||||
if (seasonal) {
|
||||
elements.push(<SeasonalView key={type} type={type} amount={purse.activityPoints.get(type)} />);
|
||||
} else {
|
||||
elements.push(<CurrencyView key={type} type={type} amount={purse.activityPoints.get(type)} short={currencyDisplayNumberShort} />);
|
||||
}
|
||||
|
||||
count++;
|
||||
}
|
||||
|
||||
return elements;
|
||||
}
|
||||
return () => window.clearTimeout(timeout);
|
||||
}, [ isOpen ]);
|
||||
|
||||
if (!purse) return null;
|
||||
|
||||
return (
|
||||
<Column alignItems="end" className="nitro-purse-container" gap={0}>
|
||||
<Flex className="nitro-purse rounded-bottom p-1">
|
||||
<Grid fullWidth gap={1}>
|
||||
<Column justifyContent="center" size={hcDisabled ? 10 : 6} gap={0}>
|
||||
<CurrencyView type={-1} amount={purse.credits} short={currencyDisplayNumberShort} />
|
||||
{getCurrencyElements(0, 2)}
|
||||
</Column>
|
||||
{!hcDisabled &&
|
||||
<Column center pointer size={4} gap={1} className="nitro-purse-subscription rounded borderhccontent" onClick={event => CreateLinkEvent('habboUI/open/hccenter')}>
|
||||
<LayoutCurrencyIcon type="hc" />
|
||||
<Text variant="white">{getClubText}</Text>
|
||||
</Column>}
|
||||
<Column justifyContent="center" size={1} gap={0}>
|
||||
<Flex center pointer fullHeight className="nitro-purse-button p-1 rounded coffecurrencybutton" onClick={event => CreateLinkEvent('help/show')}>
|
||||
<i className="nitro-icon icon-help"/>
|
||||
<Column alignItems="end" className="nitro-purse-container" gap={ 0 }>
|
||||
<div className={ `nitro-purse-shell ${ isCompact ? 'is-closed' : '' }` }>
|
||||
<div className={ `nitro-purse ${ isCompact ? 'is-closed' : '' }` }>
|
||||
<div className={ `nitro-purse__header ${ isCompact ? 'is-closed' : '' }` } onClick={ () => setIsOpen(value => !value) }>
|
||||
<Flex alignItems="center" gap={ 1 } className={ isCompact ? 'nitro-purse__header-main is-closed' : 'nitro-purse__header-main' }>
|
||||
<div className="nitro-purse__header-icon">
|
||||
<img src={ purseIcon } alt="" className="nitro-purse__header-image" />
|
||||
</div>
|
||||
</Flex>
|
||||
<Flex center pointer fullHeight className="nitro-purse-button p-1 rounded coffecurrencybutton" onClick={event => CreateLinkEvent('user-settings/toggle')}>
|
||||
<i className="nitro-icon icon-cog"/>
|
||||
</Flex>
|
||||
</Column>
|
||||
<Column justifyContent="center" size={11} gap={0}>
|
||||
{getCurrencyElements(2, -1, true)}
|
||||
</Column>
|
||||
</Grid>
|
||||
</Flex>
|
||||
|
||||
<div className={ `nitro-purse__header-toggle ${ isOpen ? 'is-open' : '' }` }>
|
||||
<FaChevronDown className="fa-icon text-[10px]" />
|
||||
</div>
|
||||
</div>
|
||||
<div className={ `nitro-purse__content ${ isOpen ? 'is-open' : 'is-closed' }` }>
|
||||
<div className={ `nitro-purse__summary nitro-purse__summary--compact ${ hcDisabled ? 'is-no-hc' : '' }` }>
|
||||
<div className="nitro-purse__primary">
|
||||
<CurrencyView type={ -1 } amount={ purse.credits } short={ currencyDisplayNumberShort } />
|
||||
{ primaryCurrencies.map(type => <CurrencyView key={ type } type={ type } amount={ purse.activityPoints.get(type) || 0 } short={ currencyDisplayNumberShort } />) }
|
||||
</div>
|
||||
{ !hcDisabled &&
|
||||
<div className="nitro-purse-subscription" onClick={ event => { event.stopPropagation(); CreateLinkEvent('habboUI/open/hccenter'); } }>
|
||||
<div className="nitro-purse-subscription__icon">
|
||||
<LayoutCurrencyIcon type="hc" />
|
||||
</div>
|
||||
<div className="nitro-purse-subscription__copy">
|
||||
<Text variant="white" className="nitro-purse-subscription__label">HC</Text>
|
||||
<Text variant="white" className="nitro-purse-subscription__value">{ getClubText }</Text>
|
||||
</div>
|
||||
</div> }
|
||||
<div className="nitro-purse__actions">
|
||||
<button type="button" className="nitro-purse__action-button nitro-purse__action-button--help" onClick={ event => { event.stopPropagation(); CreateLinkEvent('help/show'); } } title={ LocalizeText('help.button.name') }>
|
||||
<FaQuestionCircle />
|
||||
</button>
|
||||
<button type="button" className="nitro-purse__action-button nitro-purse__action-button--settings" onClick={ event => { event.stopPropagation(); CreateLinkEvent('user-settings/toggle'); } } title={ LocalizeText('widget.memenu.settings.title') }>
|
||||
<i className="nitro-icon icon-cog" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{ seasonalCurrencies.length > 0 &&
|
||||
<div className="nitro-purse__seasonal">
|
||||
{ seasonalCurrencies.map(type => <SeasonalView key={ type } type={ type } amount={ purse.activityPoints.get(type) || 0 } />) }
|
||||
</div> }
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { FC } from 'react';
|
||||
import { GetConfigurationValue, LocalizeFormattedNumber, LocalizeText } from '../../../api';
|
||||
import { Flex, LayoutCurrencyIcon, Text } from '../../../common';
|
||||
import { Flex, Text } from '../../../common';
|
||||
|
||||
interface SeasonalViewProps {
|
||||
type: number;
|
||||
@@ -10,6 +10,8 @@ interface SeasonalViewProps {
|
||||
export const SeasonalView: FC<SeasonalViewProps> = props => {
|
||||
const { type = -1, amount = -1 } = props;
|
||||
const seasonalColor = GetConfigurationValue<string>('currency.seasonal.color', 'blue');
|
||||
const formattedAmount = LocalizeFormattedNumber(amount);
|
||||
const iconUrl = GetConfigurationValue<string>('currency.asset.icon.url', '').replace('%type%', type.toString());
|
||||
|
||||
return (
|
||||
<Flex
|
||||
@@ -17,22 +19,21 @@ export const SeasonalView: FC<SeasonalViewProps> = props => {
|
||||
justifyContent="between"
|
||||
className={`nitro-purse-seasonal-currency nitro-notification ${seasonalColor}`}
|
||||
>
|
||||
<Flex fullWidth>
|
||||
<Flex fullWidth className="seasonal-row">
|
||||
<Flex className="nitro-seasonal-box seasonal-image-padding">
|
||||
<img src={ iconUrl } alt="" className="seasonal-image" />
|
||||
</Flex>
|
||||
<Text truncate fullWidth variant="white" className="seasonal-text-padding seasonal-text">
|
||||
{LocalizeText(`purse.seasonal.currency.${type}`)}
|
||||
</Text>
|
||||
<Text
|
||||
truncate
|
||||
variant="white"
|
||||
className="seasonal-amount text-end"
|
||||
title={amount > 99999 ? LocalizeFormattedNumber(amount) : ''}
|
||||
title={formattedAmount}
|
||||
>
|
||||
{amount > 99999 ? '99 999' : LocalizeFormattedNumber(amount)}
|
||||
{formattedAmount}
|
||||
</Text>
|
||||
<Flex className="nitro-seasonal-box seasonal-image-padding">
|
||||
<LayoutCurrencyIcon type={type} />
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
@@ -10,8 +10,8 @@ import { RoomPromotesWidgetView } from '../room/widgets/room-promotes/RoomPromot
|
||||
export const RightSideView: FC<{}> = props =>
|
||||
{
|
||||
return (
|
||||
<div className="absolute top-0 right-[10px] min-w-[200px] max-w-[200px] h-[calc(100%-55px)] pointer-events-none">
|
||||
<Column gap={ 1 } position="relative">
|
||||
<div className="absolute top-0 right-[8px] z-10 w-[min(188px,calc(100vw-16px))] sm:right-[10px] sm:w-[min(188px,calc(100vw-20px))] h-[calc(100%-55px)] pointer-events-none">
|
||||
<Column gap={ 1 } position="relative" className="w-full">
|
||||
<PurseView />
|
||||
<GroupRoomInformationView />
|
||||
<MysteryBoxExtensionView />
|
||||
|
||||
@@ -14,7 +14,7 @@ export const ChatInputView: FC<{}> = props =>
|
||||
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<HTMLInputElement>();
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const { isVisible: commandSelectorVisible, filteredCommands, selectedIndex, setSelectedIndex, moveUp, moveDown, selectCurrent, close: closeCommandSelector } = useChatCommandSelector(chatValue);
|
||||
|
||||
const chatModeIdWhisper = useMemo(() => LocalizeText('widgets.chatinput.mode.whisper'), []);
|
||||
@@ -279,7 +279,7 @@ export const ChatInputView: FC<{}> = props =>
|
||||
|
||||
return (
|
||||
createPortal(
|
||||
<div className="nitro-chat-input-container flex justify-between items-center relative h-10 border-2 border-black bg-gray-200 pr-2.5 w-full overflow-visible rounded-lg">
|
||||
<div className="nitro-chat-input-container relative flex h-[38px] w-full items-center justify-between overflow-visible rounded-[12px] border-2 border-black bg-white pr-[8px]">
|
||||
{ commandSelectorVisible &&
|
||||
<ChatInputCommandSelectorView
|
||||
commands={ filteredCommands }
|
||||
@@ -289,7 +289,7 @@ export const ChatInputView: FC<{}> = props =>
|
||||
/> }
|
||||
<div className="flex-1 items-center input-sizer">
|
||||
{ !floodBlocked &&
|
||||
<input ref={ inputRef } className="[font-size:inherit] placeholder-[#6c757d] bg-transparent border-none focus:border-current focus:shadow-none focus:ring-0 " maxLength={ maxChatLength } placeholder={ LocalizeText('widgets.chatinput.default') } type="text" value={ chatValue } onChange={ event => updateChatInput(event.target.value) } onMouseDown={ event => setInputFocus() } /> }
|
||||
<input ref={ inputRef } className="w-full border-none bg-transparent px-[10px] text-[0.86rem] text-black placeholder:text-[#6c757d] focus:border-current focus:shadow-none focus:ring-0" maxLength={ maxChatLength } placeholder={ LocalizeText('widgets.chatinput.default') } type="text" value={ chatValue } onChange={ event => updateChatInput(event.target.value) } onMouseDown={ event => setInputFocus() } /> }
|
||||
{ floodBlocked &&
|
||||
<Text variant="danger">{ LocalizeText('chat.input.alert.flood', [ 'time' ], [ floodBlockedSeconds.toString() ]) } </Text> }
|
||||
</div>
|
||||
|
||||
@@ -6,6 +6,7 @@ import { GetConfigurationValue, LocalizeText, SendMessageComposer, SetLocalStora
|
||||
import { Text } from '../../../../common';
|
||||
import { useMessageEvent, useNavigator, useRoom } from '../../../../hooks';
|
||||
import { getRegisteredPlugins, INitroPlugin, subscribePlugins } from '../../../plugins/NitroPluginApi';
|
||||
import { FaChevronLeft, FaChevronRight } from 'react-icons/fa';
|
||||
|
||||
export const RoomToolsWidgetView: FC<{}> = props => {
|
||||
const [areBubblesMuted, setAreBubblesMuted] = useState(false);
|
||||
@@ -15,6 +16,7 @@ export const RoomToolsWidgetView: FC<{}> = props => {
|
||||
const [roomTags, setRoomTags] = useState<string[]>(null);
|
||||
const [isOpen, setIsOpen] = useState<boolean>(false);
|
||||
const [isOpenHistory, setIsOpenHistory] = useState<boolean>(false);
|
||||
const [isCollapsed, setIsCollapsed] = useState<boolean>(false);
|
||||
const [roomHistory, setRoomHistory] = useState<{ roomId: number, roomName: string }[]>([]);
|
||||
const [plugins, setPlugins] = useState<INitroPlugin[]>([]);
|
||||
const { navigatorData = null } = useNavigator();
|
||||
@@ -79,10 +81,11 @@ export const RoomToolsWidgetView: FC<{}> = props => {
|
||||
|
||||
const onChangeRoomHistory = (roomId: number, roomName: string) => {
|
||||
let newStorage = JSON.parse(window.localStorage.getItem('nitro.room.history') || '[]');
|
||||
if (newStorage.some((room: { roomId: number }) => room.roomId === roomId)) return;
|
||||
|
||||
if (newStorage.length >= 10) newStorage.shift();
|
||||
newStorage = [...newStorage, { roomId, roomName }];
|
||||
newStorage = newStorage.filter((room: { roomId: number }) => room.roomId !== roomId);
|
||||
newStorage = [ ...newStorage, { roomId, roomName } ];
|
||||
|
||||
if (newStorage.length > 10) newStorage = newStorage.slice(-10);
|
||||
|
||||
setRoomHistory(newStorage);
|
||||
SetLocalStorage('nitro.room.history', newStorage);
|
||||
@@ -99,48 +102,55 @@ export const RoomToolsWidgetView: FC<{}> = props => {
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
setIsOpen(true);
|
||||
const timeout = setTimeout(() => setIsOpen(false), 5000);
|
||||
return () => clearTimeout(timeout);
|
||||
if(roomName || roomOwner || (roomTags && roomTags.length)) setIsOpen(true);
|
||||
}, [roomName, roomOwner, roomTags]);
|
||||
|
||||
useEffect(() => {
|
||||
if(!isCollapsed && (roomName || roomOwner || (roomTags && roomTags.length))) setIsOpen(true);
|
||||
}, [ isCollapsed, roomName, roomOwner, roomTags ]);
|
||||
|
||||
useEffect(() => {
|
||||
setRoomHistory(JSON.parse(window.localStorage.getItem('nitro.room.history') || '[]'));
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const handleTabClose = () => {
|
||||
window.localStorage.removeItem('nitro.room.history');
|
||||
};
|
||||
window.addEventListener('beforeunload', handleTabClose);
|
||||
return () => window.removeEventListener('beforeunload', handleTabClose);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="flex space-x-2 nitro-room-tools-container">
|
||||
<div className="flex flex-col items-center justify-center p-2 nitro-room-tools">
|
||||
<div className="cursor-pointer nitro-icon icon-cog" title={LocalizeText('room.settings.button.text')} onClick={() => handleToolClick('settings')} />
|
||||
<div className={classNames('cursor-pointer', 'nitro-icon', (!isZoomedIn && 'icon-zoom-less'), (isZoomedIn && 'icon-zoom-more'))} title={LocalizeText('room.zoom.button.text')} onClick={() => handleToolClick('zoom')} />
|
||||
<div className="cursor-pointer nitro-icon icon-chat-history" title={LocalizeText('room.chathistory.button.text')} onClick={() => handleToolClick('chat_history')} />
|
||||
<div className={classNames('cursor-pointer', 'nitro-icon', (areBubblesMuted ? 'icon-chat-disablebubble' : 'icon-chat-enablebubble'))} title={areBubblesMuted ? LocalizeText('room.unmute.button.text') : LocalizeText('room.mute.button.text')} onClick={() => handleToolClick('hiddenbubbles')} />
|
||||
<div className="nitro-room-tools-container">
|
||||
<div className="nitro-room-tools-rail">
|
||||
<button type="button" className={ `nitro-room-tools-toggle ${ isCollapsed ? 'is-collapsed' : 'is-open' }` } onClick={ () => setIsCollapsed(value => !value) } title={ isCollapsed ? 'Apri strumenti stanza' : 'Chiudi strumenti stanza' }>
|
||||
{ isCollapsed ? <FaChevronRight className="text-[11px]" /> : <FaChevronLeft className="text-[11px]" /> }
|
||||
</button>
|
||||
<AnimatePresence initial={ false }>
|
||||
{ !isCollapsed &&
|
||||
<motion.div
|
||||
initial={ { opacity: 0, x: -10, scale: 0.96 } }
|
||||
animate={ { opacity: 1, x: 0, scale: 1 } }
|
||||
exit={ { opacity: 0, x: -10, scale: 0.96 } }
|
||||
transition={ { duration: 0.26, ease: [ 0.16, 1, 0.3, 1 ] } }
|
||||
className="flex flex-col items-center justify-center p-2 nitro-room-tools">
|
||||
<div className="cursor-pointer nitro-icon icon-cog" title={LocalizeText('room.settings.button.text')} onClick={() => handleToolClick('settings')} />
|
||||
<div className={classNames('cursor-pointer', 'nitro-icon', (!isZoomedIn && 'icon-zoom-less'), (isZoomedIn && 'icon-zoom-more'))} title={LocalizeText('room.zoom.button.text')} onClick={() => handleToolClick('zoom')} />
|
||||
<div className="cursor-pointer nitro-icon icon-chat-history" title={LocalizeText('room.chathistory.button.text')} onClick={() => handleToolClick('chat_history')} />
|
||||
<div className={classNames('cursor-pointer', 'nitro-icon', (areBubblesMuted ? 'icon-chat-disablebubble' : 'icon-chat-enablebubble'))} title={areBubblesMuted ? LocalizeText('room.unmute.button.text') : LocalizeText('room.mute.button.text')} onClick={() => handleToolClick('hiddenbubbles')} />
|
||||
|
||||
{navigatorData.canRate && (
|
||||
<div className="cursor-pointer nitro-icon icon-like-room" title={LocalizeText('room.like.button.text')} onClick={() => handleToolClick('like_room')} />
|
||||
)}
|
||||
<div className="cursor-pointer nitro-icon icon-room-link" title={LocalizeText('navigator.embed.caption')} onClick={() => handleToolClick('toggle_room_link')} />
|
||||
<div className="cursor-pointer nitro-icon icon-room-history-enabled" title={LocalizeText('room.history.button.tooltip')} onClick={() => handleToolClick('room_history')} />
|
||||
{plugins.map(plugin => (
|
||||
<div
|
||||
key={plugin.name}
|
||||
className={`cursor-pointer nitro-icon ${plugin.icon || 'icon-cog'}`}
|
||||
title={plugin.label}
|
||||
onClick={() => plugin.onOpen()}
|
||||
/>
|
||||
))}
|
||||
{navigatorData.canRate && (
|
||||
<div className="cursor-pointer nitro-icon icon-like-room" title={LocalizeText('room.like.button.text')} onClick={() => handleToolClick('like_room')} />
|
||||
)}
|
||||
<div className="cursor-pointer nitro-icon icon-room-link" title={LocalizeText('navigator.embed.caption')} onClick={() => handleToolClick('toggle_room_link')} />
|
||||
<div className="cursor-pointer nitro-icon icon-room-history-enabled" title={LocalizeText('room.history.button.tooltip')} onClick={() => handleToolClick('room_history')} />
|
||||
{plugins.map(plugin => (
|
||||
<div
|
||||
key={plugin.name}
|
||||
className={`cursor-pointer nitro-icon ${plugin.icon || 'icon-cog'}`}
|
||||
title={plugin.label}
|
||||
onClick={() => plugin.onOpen()}
|
||||
/>
|
||||
))}
|
||||
</motion.div> }
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
<div className="flex flex-col justify-center">
|
||||
<div className="flex flex-col justify-center nitro-room-tools-side-container">
|
||||
<AnimatePresence>
|
||||
{isOpen && (
|
||||
{(!isCollapsed && (isOpen || !!roomName || !!roomOwner || !!(roomTags && roomTags.length))) && (
|
||||
<motion.div initial={{ x: -100 }} animate={{ x: 0 }} exit={{ x: -100 }} transition={{ duration: 0.3 }}>
|
||||
<div className="flex flex-col items-center justify-center">
|
||||
<div className="flex flex-col px-3 py-2 rounded nitro-room-tools-info">
|
||||
@@ -161,7 +171,7 @@ export const RoomToolsWidgetView: FC<{}> = props => {
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
{isOpenHistory && (
|
||||
{(!isCollapsed && isOpenHistory) && (
|
||||
<motion.div initial={{ x: -100 }} animate={{ x: 0 }} exit={{ x: -100 }} transition={{ duration: 0.3 }} className="nitro-room-tools-history">
|
||||
<div className="flex flex-col px-3 py-2 rounded nitro-room-history">
|
||||
{roomHistory.map(history => (
|
||||
|
||||
@@ -11,7 +11,7 @@ export const ToolbarItemView = forwardRef<HTMLDivElement, PropsWithChildren<{
|
||||
<div
|
||||
ref={ ref }
|
||||
className={ classNames(
|
||||
'cursor-pointer relative',
|
||||
'relative h-[32px] w-[32px] shrink-0 cursor-pointer bg-center bg-no-repeat transition-transform duration-200 ease-out hover:-translate-y-[1px] active:translate-y-0',
|
||||
`nitro-icon icon-${ icon }`,
|
||||
className
|
||||
) }
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { CreateLinkEvent, GetRoomEngine, GetSessionDataManager, MouseEventType, RoomObjectCategory } from '@nitrots/nitro-renderer';
|
||||
import { CreateLinkEvent, GetRoomEngine, GetSessionDataManager, RoomObjectCategory } from '@nitrots/nitro-renderer';
|
||||
import { Dispatch, FC, PropsWithChildren, SetStateAction, useEffect, useRef } from 'react';
|
||||
import { DispatchUiEvent, GetConfigurationValue, GetRoomSession, GetUserProfile, LocalizeText } from '../../api';
|
||||
import { Flex, LayoutItemCountView } from '../../common';
|
||||
import { LayoutItemCountView } from '../../common';
|
||||
import { GuideToolEvent } from '../../events';
|
||||
import { ToolbarItemView } from './ToolbarItemView';
|
||||
|
||||
export const ToolbarMeView: FC<PropsWithChildren<{
|
||||
useGuideTool: boolean;
|
||||
@@ -10,8 +11,8 @@ export const ToolbarMeView: FC<PropsWithChildren<{
|
||||
setMeExpanded: Dispatch<SetStateAction<boolean>>;
|
||||
}>> = props =>
|
||||
{
|
||||
const { useGuideTool = false, unseenAchievementCount = 0, setMeExpanded = null, children = null, ...rest } = props;
|
||||
const elementRef = useRef<HTMLDivElement>();
|
||||
const { useGuideTool = false, unseenAchievementCount = 0, children = null } = props;
|
||||
const elementRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
@@ -22,29 +23,22 @@ export const ToolbarMeView: FC<PropsWithChildren<{
|
||||
GetRoomEngine().selectRoomObject(roomSession.roomId, roomSession.ownRoomIndex, RoomObjectCategory.UNIT);
|
||||
}, []);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
const onClick = (event: MouseEvent) => setMeExpanded(false);
|
||||
|
||||
document.addEventListener('click', onClick);
|
||||
|
||||
return () => document.removeEventListener(MouseEventType.MOUSE_CLICK, onClick);
|
||||
}, [ setMeExpanded ]);
|
||||
|
||||
return (
|
||||
<Flex alignItems="center" className="absolute bottom-[60px] left-[33px] bg-[rgba(20,20,20,.95)] border border-[solid] border-[#101010] [box-shadow:inset_2px_2px_rgba(255,255,255,.1),inset_-2px_-2px_rgba(255,255,255,.1)] rounded-[$border-radius] p-2" gap={ 2 } innerRef={ elementRef }>
|
||||
{ (GetConfigurationValue('guides.enabled') && useGuideTool) &&
|
||||
<div className="navigation-item relative nitro-icon icon-me-helper-tool cursor-pointer" onClick={ event => DispatchUiEvent(new GuideToolEvent(GuideToolEvent.TOGGLE_GUIDE_TOOL)) } /> }
|
||||
<div className="navigation-item relative nitro-icon icon-me-achievements cursor-pointer" onClick={ event => CreateLinkEvent('achievements/toggle') }>
|
||||
{ (unseenAchievementCount > 0) &&
|
||||
<LayoutItemCountView count={ unseenAchievementCount } /> }
|
||||
<div className="w-fit max-w-[min(calc(100vw-16px),520px)] rounded-[12px] border border-white/8 bg-[rgba(10,10,12,0.58)] px-[10px] py-[7px] shadow-[0_10px_24px_rgba(0,0,0,0.2)]" ref={ elementRef }>
|
||||
<div className="flex items-center gap-[8px] overflow-x-auto overflow-y-visible whitespace-nowrap">
|
||||
{ (GetConfigurationValue('guides.enabled') && useGuideTool) &&
|
||||
<ToolbarItemView icon="me-helper-tool" onClick={ event => DispatchUiEvent(new GuideToolEvent(GuideToolEvent.TOGGLE_GUIDE_TOOL)) } title={ LocalizeText('guide.help.button.label') } /> }
|
||||
<ToolbarItemView icon="me-achievements" onClick={ event => CreateLinkEvent('achievements/toggle') } title={ LocalizeText('toolbar.icon.label.achievements') }>
|
||||
{ (unseenAchievementCount > 0) &&
|
||||
<LayoutItemCountView count={ unseenAchievementCount } /> }
|
||||
</ToolbarItemView>
|
||||
<ToolbarItemView icon="me-profile" onClick={ event => GetUserProfile(GetSessionDataManager().userId) } title={ LocalizeText('toolbar.icon.label.memenu') } />
|
||||
<ToolbarItemView icon="me-rooms" onClick={ event => CreateLinkEvent('navigator/search/myworld_view') } title={ LocalizeText('navigator.myworlds') } />
|
||||
<ToolbarItemView icon="me-clothing" onClick={ event => CreateLinkEvent('avatar-editor/toggle') } title={ LocalizeText('widget.memenu.settings.avatar') } />
|
||||
<ToolbarItemView icon="me-settings" onClick={ event => CreateLinkEvent('user-settings/toggle') } title={ LocalizeText('widget.memenu.settings.title') } />
|
||||
<ToolbarItemView icon="me-forums" onClick={ event => CreateLinkEvent('groupforum/toggle') } title={ LocalizeText('toolbar.icon.label.forums') } />
|
||||
{ children }
|
||||
</div>
|
||||
<div className="navigation-item relative nitro-icon icon-me-profile cursor-pointer" onClick={ event => GetUserProfile(GetSessionDataManager().userId) } />
|
||||
<div className="navigation-item relative nitro-icon icon-me-rooms cursor-pointer" onClick={ event => CreateLinkEvent('navigator/search/myworld_view') } />
|
||||
<div className="navigation-item relative nitro-icon icon-me-clothing cursor-pointer" onClick={ event => CreateLinkEvent('avatar-editor/toggle') } />
|
||||
<div className="navigation-item relative nitro-icon icon-me-settings cursor-pointer" onClick={ event => CreateLinkEvent('user-settings/toggle') } />
|
||||
<div className="navigation-item relative nitro-icon icon-me-forums cursor-pointer" onClick={ event => CreateLinkEvent('groupforum/toggle') } title={ LocalizeText('toolbar.icon.label.forums') } />
|
||||
{ children }
|
||||
</Flex>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -7,10 +7,23 @@ import { useAchievements, useFriends, useInventoryUnseenTracker, useMessageEvent
|
||||
import { ToolbarItemView } from './ToolbarItemView';
|
||||
import { ToolbarMeView } from './ToolbarMeView';
|
||||
|
||||
const containerVariants = {
|
||||
hidden: {},
|
||||
visible: { transition: { staggerChildren: 0.05 } },
|
||||
exit: { transition: { staggerChildren: 0.03, staggerDirection: -1 as const } }
|
||||
};
|
||||
|
||||
const itemVariants = {
|
||||
hidden: { opacity: 0, y: 10, scale: 0.8 },
|
||||
visible: { opacity: 1, y: 0, scale: 1, transition: { type: 'spring', stiffness: 400, damping: 22 } },
|
||||
exit: { opacity: 0, y: 6, scale: 0.85, transition: { duration: 0.1 } }
|
||||
};
|
||||
|
||||
export const ToolbarView: FC<{ isInRoom: boolean }> = props =>
|
||||
{
|
||||
const { isInRoom } = props;
|
||||
const [ isMeExpanded, setMeExpanded ] = useState(false);
|
||||
const [ isToolbarOpen, setIsToolbarOpen ] = useState(false);
|
||||
const [ useGuideTool, setUseGuideTool ] = useState(false);
|
||||
const { userFigure = null } = useSessionInfo();
|
||||
const { getFullCount = 0 } = useInventoryUnseenTracker();
|
||||
@@ -19,6 +32,8 @@ export const ToolbarView: FC<{ isInRoom: boolean }> = props =>
|
||||
const { iconState = MessengerIconState.HIDDEN } = useMessenger();
|
||||
const { openMonitor, showToolbarButton } = useWiredTools();
|
||||
const isMod = GetSessionDataManager().isModerator;
|
||||
const hasDesktopUnifiedShell = (isInRoom && isToolbarOpen);
|
||||
const showDesktopShell = (isToolbarOpen || !isInRoom);
|
||||
|
||||
useMessageEvent<PerkAllowancesMessageEvent>(PerkAllowancesMessageEvent, event =>
|
||||
{
|
||||
@@ -42,13 +57,11 @@ export const ToolbarView: FC<{ isInRoom: boolean }> = props =>
|
||||
|
||||
const targetBounds = target.getBoundingClientRect();
|
||||
const imageBounds = image.getBoundingClientRect();
|
||||
|
||||
const left = (imageBounds.x - targetBounds.x);
|
||||
const top = (imageBounds.y - targetBounds.y);
|
||||
const squared = Math.sqrt(((left * left) + (top * top)));
|
||||
const wait = (500 - Math.abs(((((1 / squared) * 100) * 500) * 0.5)));
|
||||
const height = 20;
|
||||
|
||||
const motionName = (`ToolbarBouncing[${ iconName }]`);
|
||||
|
||||
if(!Motions.getMotionByTag(motionName))
|
||||
@@ -66,55 +79,266 @@ export const ToolbarView: FC<{ isInRoom: boolean }> = props =>
|
||||
|
||||
return (
|
||||
<>
|
||||
<AnimatePresence> { isMeExpanded && ( <motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} transition={{ duration: 0.3 }}>
|
||||
<ToolbarMeView setMeExpanded={ setMeExpanded } unseenAchievementCount={ getTotalUnseen } useGuideTool={ useGuideTool } />
|
||||
</motion.div> )}
|
||||
</AnimatePresence>
|
||||
<Flex alignItems="center" className="absolute bottom-0 left-0 w-full h-[55px] bg-[rgba(28,28,32,.95)] [box-shadow:inset_0_5px_#22222799,inset_0_-4px_#12121599] py-1 px-3" gap={ 2 }>
|
||||
<Flex alignItems="center" gap={ 2 } className="flex-shrink-0">
|
||||
<Flex center pointer className={ 'relative w-[50px] h-[45px] overflow-hidden ' + (isMeExpanded ? 'active ' : '') } onClick={ event =>
|
||||
{
|
||||
setMeExpanded(!isMeExpanded);
|
||||
event.stopPropagation();
|
||||
} }>
|
||||
<LayoutAvatarImageView className="-ml-[5px] mt-[25px]" direction={ 2 } figure={ userFigure } position="absolute" />
|
||||
{ (getTotalUnseen > 0) &&
|
||||
<LayoutItemCountView count={ getTotalUnseen } /> }
|
||||
</Flex>
|
||||
{ isInRoom &&
|
||||
<ToolbarItemView icon="habbo" onClick={ event => VisitDesktop() } /> }
|
||||
{ !isInRoom &&
|
||||
<ToolbarItemView icon="house" onClick={ event => CreateLinkEvent('navigator/goto/home') } /> }
|
||||
<ToolbarItemView icon="rooms" onClick={ event => CreateLinkEvent('navigator/toggle') } />
|
||||
{ GetConfigurationValue('game.center.enabled') &&
|
||||
<ToolbarItemView icon="game" onClick={ event => CreateLinkEvent('games/toggle') } /> }
|
||||
<ToolbarItemView icon="catalog" onClick={ event => CreateLinkEvent('catalog/toggle') } />
|
||||
<ToolbarItemView icon="inventory" onClick={ event => CreateLinkEvent('inventory/toggle') }>
|
||||
{ (getFullCount > 0) &&
|
||||
<LayoutItemCountView count={ getFullCount } /> }
|
||||
</ToolbarItemView>
|
||||
{ (isInRoom && showToolbarButton) &&
|
||||
<ToolbarItemView icon="wired-tools" onClick={ openMonitor } /> }
|
||||
{ isInRoom &&
|
||||
<ToolbarItemView icon="camera" onClick={ event => CreateLinkEvent('camera/toggle') } /> }
|
||||
{ isMod &&
|
||||
<ToolbarItemView icon="modtools" onClick={ event => CreateLinkEvent('mod-tools/toggle') } /> }
|
||||
{ isMod &&
|
||||
<ToolbarItemView icon="furnieditor" onClick={ event => CreateLinkEvent('furni-editor/toggle') } /> }
|
||||
</Flex>
|
||||
<Flex alignItems="center" justifyContent="center" className="flex-1 min-w-0 max-w-[600px] mx-auto" id="toolbar-chat-input-container" />
|
||||
<Flex alignItems="center" gap={ 2 } className="flex-shrink-0">
|
||||
<Flex gap={ 2 }>
|
||||
<ToolbarItemView icon="friendall" onClick={ event => CreateLinkEvent('friends/toggle') }>
|
||||
{ (requests.length > 0) &&
|
||||
<LayoutItemCountView count={ requests.length } /> }
|
||||
</ToolbarItemView>
|
||||
{ ((iconState === MessengerIconState.SHOW) || (iconState === MessengerIconState.UNREAD)) &&
|
||||
<ToolbarItemView className={ (iconState === MessengerIconState.UNREAD) && 'is-unseen' } icon="message" onClick={ event => OpenMessengerChat() } /> }
|
||||
</Flex>
|
||||
<div className="hidden lg:block" id="toolbar-friend-bar-container" />
|
||||
</Flex>
|
||||
</Flex>
|
||||
<style>{ TOOLBAR_STYLES }</style>
|
||||
|
||||
{ isInRoom &&
|
||||
<div className={ `fixed bottom-0 left-0 right-0 z-40 flex h-[52px] items-end px-0 pt-[2px] pb-0 pointer-events-none md:left-1/2 md:right-auto md:h-[52px] md:w-[420px] md:-translate-x-1/2 md:items-center md:px-[6px] md:py-[4px] lg:w-[460px] ${ isToolbarOpen ? (hasDesktopUnifiedShell ? 'md:rounded-none md:border-0 md:bg-transparent md:shadow-none rounded-t-[12px] border border-b-0 border-white/8 bg-[rgba(10,10,12,0.58)] shadow-[0_-6px_18px_rgba(0,0,0,0.18)]' : 'rounded-t-[12px] border border-b-0 border-white/8 bg-[rgba(10,10,12,0.58)] shadow-[0_-6px_18px_rgba(0,0,0,0.18)]') : 'border-0 bg-transparent shadow-none md:border-0 md:bg-transparent md:shadow-none' }` }>
|
||||
<motion.div
|
||||
className="tb-toggle pointer-events-auto mr-2 mb-[4px] flex-shrink-0 md:mb-0"
|
||||
onClick={ () => setIsToolbarOpen(value => !value) }
|
||||
whileTap={ { scale: 0.9 } }>
|
||||
<svg
|
||||
className={ `h-3.5 w-3.5 text-white/70 transition-transform duration-300 ${ isToolbarOpen ? 'rotate-180 md:-rotate-90' : 'rotate-0 md:rotate-90' }` }
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={ 2.5 } d="M5 15l7-7 7 7" />
|
||||
</svg>
|
||||
</motion.div>
|
||||
<Flex
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
className="pointer-events-auto h-full w-full min-w-0 flex-1 px-[6px] md:px-0"
|
||||
id="toolbar-chat-input-container" />
|
||||
<div className="pointer-events-auto relative mr-[6px] shrink-0 md:hidden">
|
||||
<ToolbarItemView icon="friendall" onClick={ () => CreateLinkEvent('friends/toggle') } className="tb-icon" />
|
||||
{ (requests.length > 0) &&
|
||||
<LayoutItemCountView count={ requests.length } className="absolute -right-1 top-0" /> }
|
||||
</div>
|
||||
</div> }
|
||||
|
||||
<AnimatePresence>
|
||||
{ (isToolbarOpen || !isInRoom) &&
|
||||
<>
|
||||
{ showDesktopShell &&
|
||||
<motion.div
|
||||
key="desktop-unified-shell"
|
||||
initial={ { opacity: 0, y: 8 } }
|
||||
animate={ { opacity: 1, y: 0 } }
|
||||
exit={ { opacity: 0, y: 8 } }
|
||||
transition={ { type: 'spring', stiffness: 260, damping: 26 } }
|
||||
className="pointer-events-none fixed bottom-0 left-0 right-0 z-[39] hidden h-[52px] rounded-t-[12px] border border-b-0 border-white/8 bg-[rgba(10,10,12,0.58)] shadow-[0_-6px_18px_rgba(0,0,0,0.18)] md:block" /> }
|
||||
|
||||
<motion.div
|
||||
key="left-nav"
|
||||
initial={ { opacity: 0, x: isInRoom ? -10 : 0, y: isInRoom ? 0 : 8 } }
|
||||
animate={ { opacity: 1, x: 0, y: 0 } }
|
||||
exit={ { opacity: 0, x: isInRoom ? -10 : 0, y: isInRoom ? 0 : 8 } }
|
||||
transition={ { type: 'spring', stiffness: 300, damping: 28 } }
|
||||
className="fixed bottom-0 left-0 z-40 hidden h-[52px] max-w-[calc(50vw-242px)] items-center overflow-visible pl-3 pointer-events-auto md:flex">
|
||||
<motion.div variants={ containerVariants } initial="hidden" animate="visible" exit="exit" className={ `tb-open-shell flex h-[52px] max-w-full items-center gap-2 overflow-visible px-[8px] pt-[10px] pb-[2px] ${ showDesktopShell ? 'bg-transparent' : 'rounded-t-[10px] border border-b-0 border-white/8 bg-[rgba(10,10,12,0.58)] shadow-[0_-6px_18px_rgba(0,0,0,0.18)]' }` }>
|
||||
<motion.div variants={ itemVariants }>
|
||||
{ isInRoom
|
||||
? <ToolbarItemView icon="habbo" onClick={ () => VisitDesktop() } className="tb-icon" />
|
||||
: <ToolbarItemView icon="house" onClick={ () => CreateLinkEvent('navigator/goto/home') } className="tb-icon" /> }
|
||||
</motion.div>
|
||||
<motion.div variants={ itemVariants }>
|
||||
<ToolbarItemView icon="rooms" onClick={ () => CreateLinkEvent('navigator/toggle') } className="tb-icon" />
|
||||
</motion.div>
|
||||
{ GetConfigurationValue('game.center.enabled') &&
|
||||
<motion.div variants={ itemVariants }>
|
||||
<ToolbarItemView icon="game" onClick={ () => CreateLinkEvent('games/toggle') } className="tb-icon" />
|
||||
</motion.div> }
|
||||
<motion.div variants={ itemVariants }>
|
||||
<ToolbarItemView icon="catalog" onClick={ () => CreateLinkEvent('catalog/toggle') } className="tb-icon" />
|
||||
</motion.div>
|
||||
<motion.div variants={ itemVariants } className="relative">
|
||||
<ToolbarItemView icon="inventory" onClick={ () => CreateLinkEvent('inventory/toggle') } className="tb-icon" />
|
||||
{ (getFullCount > 0) &&
|
||||
<LayoutItemCountView count={ getFullCount } className="absolute -right-1 top-0" /> }
|
||||
</motion.div>
|
||||
<motion.div variants={ itemVariants } className="relative">
|
||||
<AnimatePresence>
|
||||
{ isMeExpanded &&
|
||||
<motion.div
|
||||
initial={ { opacity: 0, y: 6, scale: 0.97 } }
|
||||
animate={ { opacity: 1, y: 0, scale: 1 } }
|
||||
exit={ { opacity: 0, y: 6, scale: 0.97 } }
|
||||
transition={ { type: 'spring', stiffness: 420, damping: 28 } }
|
||||
className="pointer-events-auto absolute bottom-[calc(100%+8px)] left-1/2 z-50 -translate-x-1/2">
|
||||
<ToolbarMeView setMeExpanded={ setMeExpanded } unseenAchievementCount={ getTotalUnseen } useGuideTool={ useGuideTool } />
|
||||
</motion.div> }
|
||||
</AnimatePresence>
|
||||
<motion.div whileHover={ { scale: 1.08 } } whileTap={ { scale: 0.95 } } className="cursor-pointer" onClick={ event => { setMeExpanded(value => !value); event.stopPropagation(); } }>
|
||||
<LayoutAvatarImageView headOnly={ true } direction={ 2 } figure={ userFigure } className="tb-icon !h-[44px] !w-[32px] !bg-center !bg-no-repeat" style={ { marginTop: '4px' } } />
|
||||
</motion.div>
|
||||
{ (getTotalUnseen > 0) &&
|
||||
<LayoutItemCountView count={ getTotalUnseen } className="pointer-events-none absolute -right-1 -top-1 z-10" /> }
|
||||
</motion.div>
|
||||
{ (isInRoom && showToolbarButton) &&
|
||||
<motion.div variants={ itemVariants }>
|
||||
<ToolbarItemView icon="wired-tools" onClick={ openMonitor } className="tb-icon" />
|
||||
</motion.div> }
|
||||
{ isInRoom &&
|
||||
<motion.div variants={ itemVariants }>
|
||||
<ToolbarItemView icon="camera" onClick={ () => CreateLinkEvent('camera/toggle') } className="tb-icon" />
|
||||
</motion.div> }
|
||||
{ isMod &&
|
||||
<motion.div variants={ itemVariants }>
|
||||
<ToolbarItemView icon="modtools" onClick={ () => CreateLinkEvent('mod-tools/toggle') } className="tb-icon" />
|
||||
</motion.div> }
|
||||
{ isMod &&
|
||||
<motion.div variants={ itemVariants }>
|
||||
<ToolbarItemView icon="furnieditor" onClick={ () => CreateLinkEvent('furni-editor/toggle') } className="tb-icon" />
|
||||
</motion.div> }
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
|
||||
<motion.div
|
||||
key="right-nav"
|
||||
initial={ { opacity: 0, x: 10 } }
|
||||
animate={ { opacity: 1, x: 0 } }
|
||||
exit={ { opacity: 0, x: 10 } }
|
||||
transition={ { type: 'spring', stiffness: 300, damping: 28 } }
|
||||
className={ `fixed bottom-0 z-40 hidden h-[52px] max-w-[calc(50vw-242px)] items-center overflow-visible pr-3 pointer-events-auto md:flex ${ isInRoom ? 'right-0' : 'right-3' }` }>
|
||||
<motion.div variants={ containerVariants } initial="hidden" animate="visible" exit="exit" className="tb-open-shell flex h-[52px] max-w-full items-center gap-3 overflow-visible bg-transparent px-[8px] pt-[10px] pb-[2px]">
|
||||
<motion.div variants={ itemVariants } className="relative">
|
||||
<ToolbarItemView icon="friendall" onClick={ () => CreateLinkEvent('friends/toggle') } className="tb-icon" />
|
||||
{ (requests.length > 0) &&
|
||||
<LayoutItemCountView count={ requests.length } className="absolute -right-2 -top-1" /> }
|
||||
</motion.div>
|
||||
{ ((iconState === MessengerIconState.SHOW) || (iconState === MessengerIconState.UNREAD)) &&
|
||||
<motion.div variants={ itemVariants }>
|
||||
<ToolbarItemView className={ `tb-icon ${ iconState === MessengerIconState.UNREAD ? 'is-unseen animate-pulse' : '' }` } icon="message" onClick={ () => OpenMessengerChat() } />
|
||||
</motion.div> }
|
||||
<div className="mx-1 hidden h-5 w-[1px] bg-white/20 md:block" />
|
||||
<div className="hidden h-full shrink-0 md:block" id="toolbar-friend-bar-container-desktop" />
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
|
||||
<motion.div
|
||||
key="mobile-nav"
|
||||
initial={ { opacity: 0, y: 8 } }
|
||||
animate={ { opacity: 1, y: 0 } }
|
||||
exit={ { opacity: 0, y: 8 } }
|
||||
transition={ { type: 'spring', stiffness: 300, damping: 28 } }
|
||||
className={ `fixed left-1/2 z-40 flex w-[95vw] -translate-x-1/2 items-center overflow-visible pointer-events-auto md:hidden ${ isInRoom ? 'bottom-[52px] rounded-t-[12px] border border-b-0 border-white/8 bg-[rgba(10,10,12,0.58)] px-[6px] py-[4px] shadow-[0_-6px_18px_rgba(0,0,0,0.18)]' : 'bottom-0' }` }>
|
||||
<motion.div variants={ containerVariants } initial="hidden" animate="visible" exit="exit" className="tb-bar-scroll flex h-full min-w-0 flex-1 items-center gap-2 overflow-x-auto overflow-y-visible px-1">
|
||||
<motion.div variants={ itemVariants }>
|
||||
{ isInRoom
|
||||
? <ToolbarItemView icon="habbo" onClick={ () => VisitDesktop() } className="tb-icon" />
|
||||
: <ToolbarItemView icon="house" onClick={ () => CreateLinkEvent('navigator/goto/home') } className="tb-icon" /> }
|
||||
</motion.div>
|
||||
<motion.div variants={ itemVariants }>
|
||||
<ToolbarItemView icon="rooms" onClick={ () => CreateLinkEvent('navigator/toggle') } className="tb-icon" />
|
||||
</motion.div>
|
||||
{ GetConfigurationValue('game.center.enabled') &&
|
||||
<motion.div variants={ itemVariants }>
|
||||
<ToolbarItemView icon="game" onClick={ () => CreateLinkEvent('games/toggle') } className="tb-icon" />
|
||||
</motion.div> }
|
||||
<motion.div variants={ itemVariants }>
|
||||
<ToolbarItemView icon="catalog" onClick={ () => CreateLinkEvent('catalog/toggle') } className="tb-icon" />
|
||||
</motion.div>
|
||||
<motion.div variants={ itemVariants } className="relative">
|
||||
<ToolbarItemView icon="inventory" onClick={ () => CreateLinkEvent('inventory/toggle') } className="tb-icon" />
|
||||
{ (getFullCount > 0) &&
|
||||
<LayoutItemCountView count={ getFullCount } className="absolute -right-1 top-0" /> }
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
<motion.div variants={ itemVariants } className="relative mx-[2px] shrink-0">
|
||||
<AnimatePresence>
|
||||
{ isMeExpanded &&
|
||||
<motion.div
|
||||
initial={ { opacity: 0, y: 6, scale: 0.97 } }
|
||||
animate={ { opacity: 1, y: 0, scale: 1 } }
|
||||
exit={ { opacity: 0, y: 6, scale: 0.97 } }
|
||||
transition={ { type: 'spring', stiffness: 420, damping: 28 } }
|
||||
className="pointer-events-auto absolute bottom-[calc(100%+10px)] left-1/2 z-[70] -translate-x-1/2">
|
||||
<ToolbarMeView setMeExpanded={ setMeExpanded } unseenAchievementCount={ getTotalUnseen } useGuideTool={ useGuideTool } />
|
||||
</motion.div> }
|
||||
</AnimatePresence>
|
||||
<motion.div whileHover={ { scale: 1.08 } } whileTap={ { scale: 0.95 } } className="cursor-pointer" onClick={ event => { setMeExpanded(value => !value); event.stopPropagation(); } }>
|
||||
<LayoutAvatarImageView headOnly={ true } direction={ 2 } figure={ userFigure } className="tb-icon !h-[44px] !w-[32px] !bg-center !bg-no-repeat" style={ { marginTop: '4px' } } />
|
||||
</motion.div>
|
||||
{ (getTotalUnseen > 0) &&
|
||||
<LayoutItemCountView count={ getTotalUnseen } className="pointer-events-none absolute -right-1 -top-1 z-10" /> }
|
||||
</motion.div>
|
||||
<motion.div variants={ containerVariants } initial="hidden" animate="visible" exit="exit" className="tb-bar-scroll flex h-full items-center gap-2 overflow-x-auto overflow-y-visible px-1">
|
||||
{ (isInRoom && showToolbarButton) &&
|
||||
<motion.div variants={ itemVariants }>
|
||||
<ToolbarItemView icon="wired-tools" onClick={ openMonitor } className="tb-icon" />
|
||||
</motion.div> }
|
||||
{ isInRoom &&
|
||||
<motion.div variants={ itemVariants }>
|
||||
<ToolbarItemView icon="camera" onClick={ () => CreateLinkEvent('camera/toggle') } className="tb-icon" />
|
||||
</motion.div> }
|
||||
{ isMod &&
|
||||
<motion.div variants={ itemVariants }>
|
||||
<ToolbarItemView icon="modtools" onClick={ () => CreateLinkEvent('mod-tools/toggle') } className="tb-icon" />
|
||||
</motion.div> }
|
||||
{ isMod &&
|
||||
<motion.div variants={ itemVariants }>
|
||||
<ToolbarItemView icon="furnieditor" onClick={ () => CreateLinkEvent('furni-editor/toggle') } className="tb-icon" />
|
||||
</motion.div> }
|
||||
{ !isInRoom &&
|
||||
<motion.div variants={ itemVariants } className="relative">
|
||||
<ToolbarItemView icon="friendall" onClick={ () => CreateLinkEvent('friends/toggle') } className="tb-icon" />
|
||||
{ (requests.length > 0) &&
|
||||
<LayoutItemCountView count={ requests.length } className="absolute -right-2 -top-1" /> }
|
||||
</motion.div> }
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
</> }
|
||||
</AnimatePresence>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const TOOLBAR_STYLES = `
|
||||
.tb-icon {
|
||||
opacity: 1;
|
||||
transition: transform 0.15s ease;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.tb-icon:hover {
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.tb-icon:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.tb-toggle {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
flex-shrink: 0;
|
||||
border-radius: 9px;
|
||||
background: rgba(18, 16, 14, 0.80);
|
||||
backdrop-filter: blur(16px);
|
||||
-webkit-backdrop-filter: blur(16px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 3px 12px rgba(0, 0, 0, 0.5);
|
||||
transition: background 0.15s, border-color 0.15s;
|
||||
}
|
||||
|
||||
.tb-toggle:hover {
|
||||
background: rgba(30, 26, 20, 0.88);
|
||||
border-color: rgba(255, 255, 255, 0.13);
|
||||
}
|
||||
|
||||
.tb-bar-scroll {
|
||||
overflow-x: auto;
|
||||
overflow-y: visible;
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
.tb-bar-scroll::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tb-open-shell {
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
|
||||
.tb-open-shell::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -14,14 +14,14 @@ export const BadgeInfoView: FC<BadgeInfoViewProps> = props =>
|
||||
|
||||
return (
|
||||
<Flex center
|
||||
className="w-[45px] h-[45px] rounded bg-white/50 relative cursor-pointer"
|
||||
className="nitro-card-panel w-[45px] h-[45px] relative cursor-pointer"
|
||||
onMouseEnter={ () => setIsHovered(true) }
|
||||
onMouseLeave={ () => setIsHovered(false) }
|
||||
>
|
||||
<LayoutBadgeImageView badgeCode={ badgeCode } />
|
||||
{ isHovered && (
|
||||
<div className="absolute top-full left-1/2 -translate-x-1/2 mt-1 z-50 bg-white text-black rounded shadow-lg py-1 px-2 text-xs w-[180px] pointer-events-none">
|
||||
<div className="absolute -top-1 left-1/2 -translate-x-1/2 w-2 h-2 bg-white rotate-45" />
|
||||
<div className="absolute top-full left-1/2 z-50 mt-1 w-[180px] -translate-x-1/2 border border-[#c4cabf] bg-[#f2f2eb] px-2 py-1 text-xs text-black shadow-none pointer-events-none rounded-[6px]">
|
||||
<div className="absolute -top-1 left-1/2 h-2 w-2 -translate-x-1/2 rotate-45 border-l border-t border-[#c4cabf] bg-[#f2f2eb]" />
|
||||
<div className="font-bold mb-0.5">{ LocalizeBadgeName(badgeCode) }</div>
|
||||
<div className="text-gray-600">{ LocalizeBadgeDescription(badgeCode) }</div>
|
||||
</div>
|
||||
|
||||
@@ -28,7 +28,7 @@ export const RelationshipsContainerView: FC<RelationshipsContainerViewProps> = p
|
||||
<i className={ `nitro-friends-spritesheet icon-${ relationshipName }` } />
|
||||
</Flex>
|
||||
<div className="flex flex-col grow gap-0">
|
||||
<div className="flex items-center justify-between bg-white rounded px-2 py-1 h-[25px]">
|
||||
<div className="nitro-card-row flex items-center justify-between px-2 py-1 h-[25px]">
|
||||
<p className="text-sm underline pointer" onClick={ event => (relationshipInfo && (relationshipInfo.randomFriendId >= 1) && GetUserProfile(relationshipInfo.randomFriendId)) }>
|
||||
{ (!relationshipInfo || (relationshipInfo.friendCount === 0)) &&
|
||||
LocalizeText('extendedprofile.add.friends') }
|
||||
|
||||
@@ -159,7 +159,7 @@ export const UserProfileView: FC<{}> = props =>
|
||||
</NitroCard.Tabs>
|
||||
<div className="flex-1 overflow-auto p-2">
|
||||
{ activeTab === 'badge' && (
|
||||
<div className="flex flex-wrap content-start gap-2 p-2 rounded bg-muted h-full">
|
||||
<div className="nitro-card-panel flex flex-wrap content-start gap-2 p-2 h-full">
|
||||
{ userBadges && (userBadges.length > 0)
|
||||
? userBadges.map((badge, index) => (
|
||||
<BadgeInfoView key={ badge + index } badgeCode={ badge } />
|
||||
@@ -196,7 +196,7 @@ export const UserProfileView: FC<{}> = props =>
|
||||
</Flex>
|
||||
) }
|
||||
{ userRooms && userRooms.length > 0 && userRooms.map(room => (
|
||||
<Flex key={ room.roomId } alignItems="center" gap={ 2 } className="px-2 py-1.5 rounded bg-white/50 cursor-pointer hover:bg-white/80" onClick={ () => CreateRoomSession(room.roomId) }>
|
||||
<Flex key={ room.roomId } alignItems="center" gap={ 2 } className="nitro-card-row px-2 py-1.5 cursor-pointer" onClick={ () => CreateRoomSession(room.roomId) }>
|
||||
<div className="flex flex-col min-w-0 grow">
|
||||
<Text bold small truncate>{ room.roomName }</Text>
|
||||
{ room.description && <Text small truncate variant="muted">{ room.description }</Text> }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { AddLinkEventTracker, AvatarExpressionEnum, FigureUpdateEvent, FurnitureFloorUpdateEvent, FurnitureMultiStateComposer, FurnitureWallMultiStateComposer, FurnitureWallUpdateComposer, FurnitureWallUpdateEvent, GetLocalizationManager, GetRoomEngine, GetSessionDataManager, ILinkEventTracker, PetMoveComposer, RemoveLinkEventTracker, RoomControllerLevel, RoomObjectCategory, RoomObjectType, RoomObjectVariable, RoomUnitDanceEvent, RoomUnitEffectEvent, RoomUnitExpressionEvent, RoomUnitHandItemEvent, RoomUnitInfoEvent, RoomUnitLookComposer, RoomUnitStatusEvent, RoomUnitWalkComposer, UpdateFurniturePositionComposer, Vector3d } from '@nitrots/nitro-renderer';
|
||||
import { AddLinkEventTracker, AvatarExpressionEnum, FigureUpdateEvent, FurnitureFloorUpdateEvent, FurnitureMultiStateComposer, FurnitureWallMultiStateComposer, FurnitureWallUpdateComposer, FurnitureWallUpdateEvent, GetLocalizationManager, GetRoomEngine, GetSessionDataManager, ILinkEventTracker, RemoveLinkEventTracker, RoomControllerLevel, RoomObjectCategory, RoomObjectType, RoomObjectVariable, RoomUnitDanceEvent, RoomUnitEffectEvent, RoomUnitExpressionEvent, RoomUnitHandItemEvent, RoomUnitInfoEvent, RoomUnitStatusEvent, UpdateFurniturePositionComposer, Vector3d, WiredUserInspectMoveComposer } from '@nitrots/nitro-renderer';
|
||||
import { WiredMonitorDataEvent, WiredMonitorRequestComposer } from '@nitrots/nitro-renderer';
|
||||
import { FC, KeyboardEvent, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import furniInspectionIcon from '../../assets/images/wiredtools/furni.png';
|
||||
@@ -433,16 +433,6 @@ const VARIABLE_DEFINITIONS: Record<VariablesElementType, VariableDefinition[]> =
|
||||
createVariableDefinition('@chat_style', 'Context', 'Conditional')
|
||||
]
|
||||
};
|
||||
const USER_DIRECTION_VECTORS: Array<{ x: number; y: number; }> = [
|
||||
{ x: 0, y: -1 },
|
||||
{ x: 1, y: -1 },
|
||||
{ x: 1, y: 0 },
|
||||
{ x: 1, y: 1 },
|
||||
{ x: 0, y: 1 },
|
||||
{ x: -1, y: 1 },
|
||||
{ x: -1, y: 0 },
|
||||
{ x: -1, y: -1 }
|
||||
];
|
||||
const WIRED_FREEZE_EFFECT_IDS: Set<number> = new Set([ 218, 12, 11, 53, 163 ]);
|
||||
const TEAM_COLOR_NAMES: Record<number, string> = {
|
||||
1: 'red',
|
||||
@@ -641,6 +631,7 @@ export const WiredCreatorToolsView: FC<{}> = () =>
|
||||
const [ isManagedGiveOpen, setIsManagedGiveOpen ] = useState(false);
|
||||
const [ managedGiveVariableItemId, setManagedGiveVariableItemId ] = useState(0);
|
||||
const [ managedGiveValue, setManagedGiveValue ] = useState('0');
|
||||
const shouldPauseVariableSnapshotRefresh = (!!editingVariable || !!editingManagedHolderVariableId || isInspectionGiveOpen || isManagedGiveOpen);
|
||||
const [ selectedVariableKeys, setSelectedVariableKeys ] = useState<Record<VariablesElementType, string>>({
|
||||
furni: VARIABLE_DEFINITIONS.furni[0].key,
|
||||
user: VARIABLE_DEFINITIONS.user[0].key,
|
||||
@@ -1296,14 +1287,14 @@ export const WiredCreatorToolsView: FC<{}> = () =>
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!isVisible || !roomSession?.roomId || !roomSettings.canInspect) return;
|
||||
if(!isVisible || !roomSession?.roomId || !roomSettings.canInspect || shouldPauseVariableSnapshotRefresh) return;
|
||||
|
||||
requestUserVariables();
|
||||
|
||||
const interval = window.setInterval(requestUserVariables, WIRED_VARIABLES_POLL_MS);
|
||||
|
||||
return () => window.clearInterval(interval);
|
||||
}, [ isVisible, roomSession?.roomId, roomSettings.canInspect, requestUserVariables ]);
|
||||
}, [ isVisible, roomSession?.roomId, roomSettings.canInspect, requestUserVariables, shouldPauseVariableSnapshotRefresh ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
@@ -2789,9 +2780,8 @@ export const WiredCreatorToolsView: FC<{}> = () =>
|
||||
break;
|
||||
}
|
||||
|
||||
requestUserVariables();
|
||||
setEditingManagedHolderVariableId(0);
|
||||
}, [ selectedManagedVariableEntry, selectedManagedHolderVariableEntry, roomSettings.canModify, editingManagedHolderValue, variablesType, updateUserVariableValue, updateFurniVariableValue, updateRoomVariableValue, requestUserVariables ]);
|
||||
}, [ selectedManagedVariableEntry, selectedManagedHolderVariableEntry, roomSettings.canModify, editingManagedHolderValue, variablesType, updateUserVariableValue, updateFurniVariableValue, updateRoomVariableValue ]);
|
||||
const giveManagedHolderVariable = useCallback(() =>
|
||||
{
|
||||
if(!selectedManagedVariableEntry || !selectedManagedGiveDefinition || !roomSettings.canModify) return;
|
||||
@@ -2803,11 +2793,10 @@ export const WiredCreatorToolsView: FC<{}> = () =>
|
||||
if(variablesType === 'user') assignUserVariable(selectedManagedVariableEntry.entityId, selectedManagedGiveDefinition.itemId, nextValue);
|
||||
else assignFurniVariable(selectedManagedVariableEntry.entityId, selectedManagedGiveDefinition.itemId, nextValue);
|
||||
|
||||
requestUserVariables();
|
||||
setSelectedManagedHolderVariableId(selectedManagedGiveDefinition.itemId);
|
||||
setManagedGiveValue('0');
|
||||
setIsManagedGiveOpen(false);
|
||||
}, [ selectedManagedVariableEntry, selectedManagedGiveDefinition, roomSettings.canModify, variablesType, managedGiveValue, assignUserVariable, assignFurniVariable, requestUserVariables ]);
|
||||
}, [ selectedManagedVariableEntry, selectedManagedGiveDefinition, roomSettings.canModify, variablesType, managedGiveValue, assignUserVariable, assignFurniVariable ]);
|
||||
const removeManagedHolderVariable = useCallback(() =>
|
||||
{
|
||||
if(!selectedManagedVariableEntry || !selectedManagedHolderVariableEntry || !roomSettings.canModify || selectedManagedHolderVariableEntry.isReadOnly) return;
|
||||
@@ -2816,9 +2805,8 @@ export const WiredCreatorToolsView: FC<{}> = () =>
|
||||
if(variablesType === 'user') removeUserVariable(selectedManagedVariableEntry.entityId, selectedManagedHolderVariableEntry.variableItemId);
|
||||
else removeFurniVariable(selectedManagedVariableEntry.entityId, selectedManagedHolderVariableEntry.variableItemId);
|
||||
|
||||
requestUserVariables();
|
||||
setEditingManagedHolderVariableId(0);
|
||||
}, [ selectedManagedVariableEntry, selectedManagedHolderVariableEntry, roomSettings.canModify, variablesType, removeUserVariable, removeFurniVariable, requestUserVariables ]);
|
||||
}, [ selectedManagedVariableEntry, selectedManagedHolderVariableEntry, roomSettings.canModify, variablesType, removeUserVariable, removeFurniVariable ]);
|
||||
|
||||
const clearMonitorLogs = () =>
|
||||
{
|
||||
@@ -3012,28 +3000,7 @@ export const WiredCreatorToolsView: FC<{}> = () =>
|
||||
return;
|
||||
}
|
||||
|
||||
if(selectedUser.kind === 'pet')
|
||||
{
|
||||
SendMessageComposer(new PetMoveComposer(selectedUser.userId, nextX, nextY, nextDirection));
|
||||
}
|
||||
else if((selectedUser.kind === 'user') && (selectedUser.roomIndex === roomSession.ownRoomIndex))
|
||||
{
|
||||
if(editingVariable === '@direction')
|
||||
{
|
||||
const directionVector = USER_DIRECTION_VECTORS[nextDirection] ?? USER_DIRECTION_VECTORS[0];
|
||||
|
||||
SendMessageComposer(new RoomUnitLookComposer((currentLiveState.positionX + directionVector.x), (currentLiveState.positionY + directionVector.y)));
|
||||
}
|
||||
else
|
||||
{
|
||||
SendMessageComposer(new RoomUnitWalkComposer(nextX, nextY));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
cancelVariableEdit();
|
||||
return;
|
||||
}
|
||||
SendMessageComposer(new WiredUserInspectMoveComposer(selectedUser.roomIndex, nextX, nextY, nextDirection));
|
||||
|
||||
setSelectedUserLiveState({
|
||||
...currentLiveState,
|
||||
@@ -3352,10 +3319,9 @@ export const WiredCreatorToolsView: FC<{}> = () =>
|
||||
setSelectedInspectionVariableKeys(prev => ({ ...prev, furni: selectedInspectionGiveDefinition.name }));
|
||||
}
|
||||
|
||||
requestUserVariables();
|
||||
setInspectionGiveValue('0');
|
||||
setIsInspectionGiveOpen(false);
|
||||
}, [ canManageInspectionVariableAssignments, selectedInspectionGiveDefinition, inspectionGiveValue, inspectionType, selectedUser, assignUserVariable, selectedFurni, assignFurniVariable, requestUserVariables ]);
|
||||
}, [ canManageInspectionVariableAssignments, selectedInspectionGiveDefinition, inspectionGiveValue, inspectionType, selectedUser, assignUserVariable, selectedFurni, assignFurniVariable ]);
|
||||
const removeInspectionVariable = useCallback(() =>
|
||||
{
|
||||
if(!canManageInspectionVariableAssignments || !selectedInspectionCustomDefinition || selectedInspectionCustomDefinition.isReadOnly) return;
|
||||
@@ -3373,25 +3339,44 @@ export const WiredCreatorToolsView: FC<{}> = () =>
|
||||
setSelectedInspectionVariableKeys(prev => ({ ...prev, furni: '' }));
|
||||
}
|
||||
|
||||
requestUserVariables();
|
||||
setIsInspectionGiveOpen(false);
|
||||
}, [ canManageInspectionVariableAssignments, selectedInspectionCustomDefinition, inspectionType, selectedUser, removeUserVariable, selectedFurni, removeFurniVariable, requestUserVariables ]);
|
||||
}, [ canManageInspectionVariableAssignments, selectedInspectionCustomDefinition, inspectionType, selectedUser, removeUserVariable, selectedFurni, removeFurniVariable ]);
|
||||
|
||||
const onVariableInputKeyDown = (event: KeyboardEvent<HTMLInputElement>) =>
|
||||
{
|
||||
event.stopPropagation();
|
||||
|
||||
if(event.nativeEvent.stopImmediatePropagation) event.nativeEvent.stopImmediatePropagation();
|
||||
|
||||
switch(event.key)
|
||||
{
|
||||
case 'Enter':
|
||||
case 'NumpadEnter':
|
||||
event.preventDefault();
|
||||
commitVariableEdit();
|
||||
window.requestAnimationFrame(() => event.currentTarget.blur());
|
||||
return;
|
||||
case 'Escape':
|
||||
event.preventDefault();
|
||||
cancelVariableEdit();
|
||||
window.requestAnimationFrame(() => event.currentTarget.blur());
|
||||
return;
|
||||
}
|
||||
};
|
||||
const onManagedHolderValueInputKeyDown = (event: KeyboardEvent<HTMLInputElement>) =>
|
||||
{
|
||||
event.stopPropagation();
|
||||
|
||||
switch(event.key)
|
||||
{
|
||||
case 'Enter':
|
||||
case 'NumpadEnter':
|
||||
event.preventDefault();
|
||||
commitManagedHolderValueEdit();
|
||||
window.requestAnimationFrame(() => event.currentTarget.blur());
|
||||
return;
|
||||
case 'Escape':
|
||||
event.preventDefault();
|
||||
setEditingManagedHolderVariableId(0);
|
||||
window.requestAnimationFrame(() => event.currentTarget.blur());
|
||||
return;
|
||||
}
|
||||
};
|
||||
@@ -3731,15 +3716,9 @@ export const WiredCreatorToolsView: FC<{}> = () =>
|
||||
type="text"
|
||||
value={ editingValue }
|
||||
onClick={ event => event.stopPropagation() }
|
||||
onBlur={ commitVariableEdit }
|
||||
onBlur={ cancelVariableEdit }
|
||||
onChange={ event => setEditingValue(event.target.value) }
|
||||
onKeyDownCapture={ event =>
|
||||
{
|
||||
event.stopPropagation();
|
||||
|
||||
if(event.nativeEvent.stopImmediatePropagation) event.nativeEvent.stopImmediatePropagation();
|
||||
} }
|
||||
onKeyDown={ onVariableInputKeyDown } /> }
|
||||
onKeyDownCapture={ onVariableInputKeyDown } /> }
|
||||
{ (editingVariable !== variable.key) && !variable.editable && <span className={ variable.valueClassName }>{ variable.value }</span> }
|
||||
{ (editingVariable !== variable.key) && variable.editable &&
|
||||
<button
|
||||
@@ -4224,19 +4203,10 @@ export const WiredCreatorToolsView: FC<{}> = () =>
|
||||
className="w-[72px] rounded border border-[#b8b2a4] bg-white px-2 py-[2px] text-[12px]"
|
||||
type="number"
|
||||
value={ editingManagedHolderValue }
|
||||
onBlur={ () => commitManagedHolderValueEdit() }
|
||||
onBlur={ () => setEditingManagedHolderVariableId(0) }
|
||||
onChange={ event => setEditingManagedHolderValue(event.target.value) }
|
||||
onClick={ event => event.stopPropagation() }
|
||||
onKeyDown={ (event: KeyboardEvent<HTMLInputElement>) =>
|
||||
{
|
||||
if(event.key === 'Enter') commitManagedHolderValueEdit();
|
||||
|
||||
if(event.key === 'Escape')
|
||||
{
|
||||
setEditingManagedHolderVariableId(0);
|
||||
setEditingManagedHolderValue('');
|
||||
}
|
||||
} } /> }
|
||||
onKeyDownCapture={ onManagedHolderValueInputKeyDown } /> }
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user