Continue layout changes

This commit is contained in:
Bill 2024-04-16 09:33:51 -04:00
parent 6010103b90
commit f5d64fa2b4
60 changed files with 299 additions and 144 deletions

View File

@ -1,10 +1,10 @@
import { GetAssetManager, GetAvatarRenderManager, GetCommunication, GetConfiguration, GetLocalizationManager, GetRoomCameraWidgetManager, GetRoomEngine, GetRoomSessionManager, GetSessionDataManager, GetSoundManager, GetStage, GetTexturePool, GetTicker, HabboWebTools, LegacyExternalInterface, LoadGameUrlEvent, NitroLogger, NitroVersion, PrepareRenderer } from '@nitrots/nitro-renderer';
import { FC, useEffect, useState } from 'react';
import { GetUIVersion } from './api';
import { classNames } from './common';
import { MainView } from './components/MainView';
import { LoadingView } from './components/loading/LoadingView';
import { useMessageEvent } from './hooks';
import { classNames } from './layout';
NitroVersion.UI_VERSION = GetUIVersion();

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 B

View File

@ -1,4 +1,4 @@
export * from '../layout/InfiniteGrid';
export * from './AutoGrid';
export * from './Base';
export * from './Button';
@ -13,7 +13,6 @@ export * from './Text';
export * from './card';
export * from './card/accordion';
export * from './card/tabs';
export * from './classNames';
export * from './draggable-window';
export * from './layout';
export * from './layout/limited-edition';

View File

@ -65,7 +65,7 @@ export const LayoutGridItem: FC<LayoutGridItemProps> = props =>
{ (itemUniqueNumber > 0) &&
<>
<Base fit className="unique-bg-override" style={ { backgroundImage: `url(${ itemImage })` } } />
<div className="position-absolute bottom-0 unique-item-counter">
<div className="absolute bottom-0 unique-item-counter">
<LayoutLimitedEditionStyledNumberView value={ itemUniqueNumber } />
</div>
</> }

View File

@ -80,8 +80,8 @@ export const LayoutRoomPreviewerView: FC<LayoutRoomPreviewerViewProps> = props =
}, [ roomPreviewer, elementRef, height ]);
return (
<div className="room-preview-container">
<div ref={ elementRef } className="room-preview-image" style={ { height } } onClick={ onClick } />
<div className="relative w-full">
<div ref={ elementRef } className="rounded-md shadow" style={ { height } } onClick={ onClick } />
{ children }
</div>
);

View File

@ -47,7 +47,7 @@ export const AchievementsView: FC<{}> = props =>
<NitroCardView className="nitro-achievements" theme="primary-slim" uniqueKey="achievements">
<NitroCardHeaderView headerText={ LocalizeText('inventory.achievements') } onCloseClick={ event => setIsVisible(false) } />
{ selectedCategory &&
<div className="position-relative gap-3 justify-center items-center cursor-pointer">
<div className="relative gap-3 justify-center items-center cursor-pointer">
<div className="nitro-achievements-back-arrow" onClick={ event => setSelectedCategoryCode(null) } />
<Column className="flex-grow-1" gap={ 0 }>
<Text className="text-small" fontSize={ 4 } fontWeight="bold">{ LocalizeText(`quests.${ selectedCategory.code }.name`) }</Text>

View File

@ -27,7 +27,7 @@ export const AvatarEditorFigurePreviewView: FC<{}> = props =>
}
return (
<div className="flex flex-col figure-preview-container overflow-hidden position-relative">
<div className="flex flex-col figure-preview-container overflow-hidden relative">
<LayoutAvatarImageView direction={ direction } figure={ getFigureString } scale={ 2 } />
<AvatarEditorIcon className="avatar-spotlight" icon="spotlight" />
<div className="avatar-shadow" />

View File

@ -1,5 +1,5 @@
import { DetailedHTMLProps, HTMLAttributes, PropsWithChildren, forwardRef } from 'react';
import { classNames } from '../../common';
import { classNames } from '../../layout';
type AvatarIconType = 'male' | 'female' | 'clear' | 'sellable' | string;

View File

@ -1,8 +1,9 @@
import { GetAvatarRenderManager, IAvatarFigureContainer, SaveWardrobeOutfitMessageComposer } from '@nitrots/nitro-renderer';
import { FC, useCallback } from 'react';
import { GetClubMemberLevel, GetConfigurationValue, LocalizeText, SendMessageComposer } from '../../api';
import { Button, InfiniteGrid, LayoutAvatarImageView, LayoutCurrencyIcon, LayoutGridItem } from '../../common';
import { Button, LayoutAvatarImageView, LayoutCurrencyIcon } from '../../common';
import { useAvatarEditor } from '../../hooks';
import { InfiniteGrid } from '../../layout';
export const AvatarEditorWardrobeView: FC<{}> = props =>
{
@ -43,18 +44,18 @@ export const AvatarEditorWardrobeView: FC<{}> = props =>
if(figureContainer) clubLevel = GetAvatarRenderManager().getFigureClubLevel(figureContainer, gender);
return (
<LayoutGridItem className="nitro-avatar-editor-wardrobe-figure-preview" overflow="hidden" position="relative">
<InfiniteGrid.Item className="nitro-avatar-editor-wardrobe-figure-preview">
{ figureContainer &&
<LayoutAvatarImageView direction={ 2 } figure={ figureContainer.getFigureString() } gender={ gender } /> }
<div className="avatar-shadow" />
{ !hcDisabled && (clubLevel > 0) && <LayoutCurrencyIcon className="position-absolute top-1 start-1" type="hc" /> }
{ !hcDisabled && (clubLevel > 0) && <LayoutCurrencyIcon className="absolute top-1 start-1" type="hc" /> }
<div className="flex gap-1 button-container">
<Button fullWidth variant="link" onClick={ event => saveFigureAtWardrobeIndex(index) }>{ LocalizeText('avatareditor.wardrobe.save') }</Button>
{ figureContainer &&
<Button fullWidth disabled={ (clubLevel > GetClubMemberLevel()) } variant="link" onClick={ event => wearFigureAtIndex(index) }>{ LocalizeText('widget.generic_usable.button.use') }</Button> }
</div>
</LayoutGridItem>
</InfiniteGrid.Item>
)
} } overscan={ 5 } rows={ savedFigures } />
} } items={ savedFigures } overscan={ 5 } />
);
}

View File

@ -1,8 +1,9 @@
import { AvatarFigurePartType } from '@nitrots/nitro-renderer';
import { FC, useEffect, useState } from 'react';
import { AvatarEditorThumbnailsHelper, GetConfigurationValue, IAvatarEditorCategoryPartItem } from '../../../api';
import { LayoutCurrencyIcon, LayoutGridItem, LayoutGridItemProps } from '../../../common';
import { LayoutCurrencyIcon, LayoutGridItemProps } from '../../../common';
import { useAvatarEditor } from '../../../hooks';
import { InfiniteGrid } from '../../../layout';
import { AvatarEditorIcon } from '../AvatarEditorIcon';
export const AvatarEditorFigureSetItemView: FC<{
@ -46,10 +47,10 @@ export const AvatarEditorFigureSetItemView: FC<{
if(!partItem) return null;
return (
<LayoutGridItem itemActive={ isSelected } itemImage={ (partItem.isClear ? undefined : assetUrl) } style={ { flex: '1', backgroundPosition: (setType === AvatarFigurePartType.HEAD) ? 'center -35px' : 'center' } } { ...rest }>
{ !partItem.isClear && isHC && <LayoutCurrencyIcon className="position-absolute end-1 bottom-1" type="hc" /> }
<InfiniteGrid.Item itemActive={ isSelected } itemImage={ (partItem.isClear ? undefined : assetUrl) } style={ { flex: '1', backgroundPosition: (setType === AvatarFigurePartType.HEAD) ? 'center -35px' : 'center' } } { ...rest }>
{ !partItem.isClear && isHC && <LayoutCurrencyIcon className="absolute end-1 bottom-1" type="hc" /> }
{ partItem.isClear && <AvatarEditorIcon icon="clear" /> }
{ !partItem.isClear && partItem.partSet.isSellable && <AvatarEditorIcon className="end-1 bottom-1 position-absolute" icon="sellable" /> }
</LayoutGridItem>
{ !partItem.isClear && partItem.partSet.isSellable && <AvatarEditorIcon className="end-1 bottom-1 absolute" icon="sellable" /> }
</InfiniteGrid.Item>
);
}

View File

@ -1,7 +1,7 @@
import { FC } from 'react';
import { IAvatarEditorCategory, IAvatarEditorCategoryPartItem } from '../../../api';
import { InfiniteGrid } from '../../../common';
import { useAvatarEditor } from '../../../hooks';
import { InfiniteGrid } from '../../../layout';
import { AvatarEditorFigureSetItemView } from './AvatarEditorFigureSetItemView';
export const AvatarEditorFigureSetView: FC<{
@ -29,13 +29,13 @@ export const AvatarEditorFigureSetView: FC<{
}
return (
<InfiniteGrid columnCount={ columnCount } itemRender={ (item: IAvatarEditorCategoryPartItem) =>
<InfiniteGrid<IAvatarEditorCategoryPartItem> columnCount={ columnCount } itemRender={ (item: IAvatarEditorCategoryPartItem) =>
{
if(!item) return null;
return (
<AvatarEditorFigureSetItemView isSelected={ isPartItemSelected(item) } partItem={ item } setType={ category.setType } width={ `calc(100% / ${ columnCount }` } onClick={ event => selectEditorPart(category.setType, item.partSet?.id ?? -1) } />
)
} } overscan={ columnCount } rows={ category.partItems } />
} } items={ category.partItems } overscan={ columnCount } />
);
}

View File

@ -1,7 +1,8 @@
import { ColorConverter, IPartColor } from '@nitrots/nitro-renderer';
import { FC } from 'react';
import { GetConfigurationValue } from '../../../api';
import { LayoutCurrencyIcon, LayoutGridItem, LayoutGridItemProps } from '../../../common';
import { LayoutCurrencyIcon, LayoutGridItemProps } from '../../../common';
import { InfiniteGrid } from '../../../layout';
export const AvatarEditorPaletteSetItem: FC<{
setType: string;
@ -17,8 +18,8 @@ export const AvatarEditorPaletteSetItem: FC<{
const isHC = !GetConfigurationValue<boolean>('hc.disabled', false) && (partColor.clubLevel > 0);
return (
<LayoutGridItem itemHighlight className="clear-bg" itemActive={ isSelected } itemColor={ ColorConverter.int2rgb(partColor.rgb) } { ...rest }>
{ isHC && <LayoutCurrencyIcon className="position-absolute end-1 bottom-1" type="hc" /> }
</LayoutGridItem>
<InfiniteGrid.Item itemHighlight className="clear-bg" itemActive={ isSelected } itemColor={ ColorConverter.int2rgb(partColor.rgb) } { ...rest }>
{ isHC && <LayoutCurrencyIcon className="absolute end-1 bottom-1" type="hc" /> }
</InfiniteGrid.Item>
);
}

View File

@ -1,8 +1,8 @@
import { IPartColor } from '@nitrots/nitro-renderer';
import { FC } from 'react';
import { IAvatarEditorCategory } from '../../../api';
import { InfiniteGrid } from '../../../common';
import { useAvatarEditor } from '../../../hooks';
import { InfiniteGrid } from '../../../layout';
import { AvatarEditorPaletteSetItem } from './AvatarEditorPaletteSetItemView';
export const AvatarEditorPaletteSetView: FC<{
@ -24,13 +24,13 @@ export const AvatarEditorPaletteSetView: FC<{
}
return (
<InfiniteGrid columnCount={ columnCount } itemRender={ (item: IPartColor) =>
<InfiniteGrid<IPartColor> columnCount={ columnCount } itemRender={ (item: IPartColor) =>
{
if(!item) return null;
return (
<AvatarEditorPaletteSetItem isSelected={ isPartColorSelected(item) } partColor={ item } setType={ category.setType } width={ `calc(100% / ${ columnCount }` } onClick={ event => selectEditorColor(category.setType, paletteIndex, item.id) } />
)
} } overscan={ columnCount } rows={ category.colorItems[paletteIndex] } />
} } items={ category.colorItems[paletteIndex] } overscan={ columnCount } />
);
}

View File

@ -62,13 +62,13 @@ export const CameraWidgetCaptureView: FC<CameraWidgetCaptureViewProps> = props =
<Column center className="nitro-camera-capture" gap={ 0 }>
{ selectedPicture && <img alt="" className="camera-area" src={ selectedPicture.imageUrl } /> }
<div className="camera-canvas drag-handler">
<div className="position-absolute header-close" onClick={ onClose }>
<div className="absolute header-close" onClick={ onClose }>
<FaTimes className="fa-icon" />
</div>
{ !selectedPicture && <div ref={ elementRef } className="camera-area camera-view-finder" /> }
{ selectedPicture &&
<div className="camera-area camera-frame">
<div className="camera-frame-preview-actions w-100 position-absolute bottom-0 py-2 text-center">
<div className="camera-frame-preview-actions w-100 absolute bottom-0 py-2 text-center">
<button className="btn btn-success me-3" title={ LocalizeText('camera.editor.button.tooltip') } onClick={ onEdit }>{ LocalizeText('camera.editor.button.text') }</button>
<button className="btn btn-danger" onClick={ onDelete }>{ LocalizeText('camera.delete.button.text') }</button>
</div>

View File

@ -2,9 +2,10 @@ import { GetSessionDataManager, GiftReceiverNotFoundEvent, PurchaseFromCatalogAs
import { ChangeEvent, FC, useCallback, useEffect, useMemo, useState } from 'react';
import { FaChevronLeft, FaChevronRight } from 'react-icons/fa';
import { ColorUtils, LocalizeText, MessengerFriend, ProductTypeEnum, SendMessageComposer } from '../../../../api';
import { Button, Column, Flex, FormGroup, LayoutCurrencyIcon, LayoutFurniImageView, LayoutGiftTagView, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text, classNames } from '../../../../common';
import { Button, Column, Flex, FormGroup, LayoutCurrencyIcon, LayoutFurniImageView, LayoutGiftTagView, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../../common';
import { CatalogEvent, CatalogInitGiftEvent, CatalogPurchasedEvent } from '../../../../events';
import { useCatalog, useFriends, useMessageEvent, useUiEvent } from '../../../../hooks';
import { classNames } from '../../../../layout';
export const CatalogGiftView: FC<{}> = props =>
{
@ -275,7 +276,7 @@ export const CatalogGiftView: FC<{}> = props =>
{ colors.map(color => <Button key={ color.id } active={ (color.id === selectedColorId) } disabled={ !isColorable } style={ { backgroundColor: color.color } } variant="dark" onClick={ () => setSelectedColorId(color.id) } />) }
</div>
</Column>
<div className="flex justify-content-between items-center">
<div className="flex items-center justify-content-between">
<Button className="text-black" variant="link" onClick={ onClose }>
{ LocalizeText('cancel') }
</Button>

View File

@ -35,7 +35,7 @@ export const CatalogLayoutBadgeDisplayView: FC<CatalogLayoutProps> = props =>
</> }
{ currentOffer &&
<>
<div className="position-relative overflow-hidden">
<div className="relative overflow-hidden">
<CatalogViewProductWidgetView />
</div>
<Column className="flex-grow-1" gap={ 1 }>

View File

@ -150,7 +150,7 @@ export const CatalogLayoutColorGroupingView : FC<CatalogLayoutColorGroupViewProp
</> }
{ currentOffer &&
<>
<div className="position-relative overflow-hidden">
<div className="relative overflow-hidden">
<CatalogViewProductWidgetView />
<CatalogAddOnBadgeWidgetView className="bg-muted rounded bottom-1 end-1" position="absolute" />
{ currentOffer.product.furnitureData.hasIndexedColor &&

View File

@ -27,7 +27,7 @@ export const CatalogLayouGuildCustomFurniView: FC<CatalogLayoutProps> = props =>
</> }
{ currentOffer &&
<>
<div className="position-relative overflow-hidden">
<div className="relative overflow-hidden">
<CatalogViewProductWidgetView />
<CatalogGuildBadgeWidgetView className="bottom-1 end-1" position="absolute" />
</div>

View File

@ -30,7 +30,7 @@ export const CatalogLayoutSpacesView: FC<CatalogLayoutProps> = props =>
</> }
{ currentOffer &&
<>
<div className="position-relative overflow-hidden">
<div className="relative overflow-hidden">
<CatalogViewProductWidgetView />
</div>
<Column grow gap={ 1 }>

View File

@ -216,7 +216,7 @@ export const CatalogLayoutPetView: FC<CatalogLayoutProps> = props =>
</> }
{ currentOffer &&
<>
<div className="position-relative overflow-hidden">
<div className="relative overflow-hidden">
<CatalogViewProductWidgetView />
<CatalogAddOnBadgeWidgetView className="bg-muted rounded bottom-1 end-1" position="absolute" />
{ ((petIndex > -1) && (petIndex <= 7)) &&

View File

@ -45,7 +45,7 @@ export const OfferWindowView = (props: { offer: TargetedOfferData, setOpen: Disp
return <NitroCardView className="nitro-targeted-offer" theme="primary-slim" uniqueKey="targeted-offer">
<NitroCardHeaderView headerText={ LocalizeText(offer.title) } onCloseClick={ event => setOpen(false) } />
<div className="container-fluid p-1 position-relative justify-center items-center cursor-pointer gap-3 bg-danger">
<div className="container-fluid p-1 relative justify-center items-center cursor-pointer gap-3 bg-danger">
{ LocalizeText('targeted.offer.timeleft',[ 'timeleft' ],[ expirationTime() ]) }
</div>
<NitroCardContentView gap={ 1 }>
@ -68,7 +68,7 @@ export const OfferWindowView = (props: { offer: TargetedOfferData, setOpen: Disp
</Flex>
<div className="w-50 h-100" style={ { background: `url(${ GetConfigurationValue('image.library.url') + offer.imageUrl }) no-repeat center` } } />
</Flex>
<Flex column alignItems="center" className="price-ray position-absolute" justifyContent="center">
<Flex column alignItems="center" className="price-ray absolute" justifyContent="center">
<Text>{ LocalizeText('targeted.offer.price.label') }</Text>
{ offer.priceInCredits > 0 &&
<div className="flex gap-1">

View File

@ -34,7 +34,7 @@ export const FriendBarItemView: FC<{ friend: MessengerFriend }> = props =>
{
return (
<div ref={ elementRef } className="btn btn-primary friend-bar-item friend-bar-search">
<div className="friend-bar-item-head position-absolute"/>
<div className="friend-bar-item-head absolute"/>
<div className="text-truncate">{ LocalizeText('friend.bar.find.title') }</div>
</div>
);
@ -42,7 +42,7 @@ export const FriendBarItemView: FC<{ friend: MessengerFriend }> = props =>
return (
<div ref={ elementRef } className={ 'btn btn-success friend-bar-item ' + (isVisible ? 'friend-bar-item-active' : '') } onClick={ event => setVisible(prevValue => !prevValue) }>
<div className={ `friend-bar-item-head position-absolute ${ friend.id > 0 ? 'avatar': 'group' }` }>
<div className={ `friend-bar-item-head absolute ${ friend.id > 0 ? 'avatar': 'group' }` }>
{ (friend.id > 0) && <LayoutAvatarImageView direction={ 2 } figure={ friend.figure } headOnly={ true } /> }
{ (friend.id <= 0) && <LayoutBadgeImageView badgeCode={ friend.figure } isGroup={ true } /> }
</div>

View File

@ -2,8 +2,9 @@ import { AddLinkEventTracker, GetSessionDataManager, GroupAdminGiveComposer, Gro
import { FC, useCallback, useEffect, useState } from 'react';
import { FaChevronLeft, FaChevronRight } from 'react-icons/fa';
import { GetUserProfile, LocalizeText, SendMessageComposer } from '../../../api';
import { Button, classNames, Column, Flex, Grid, LayoutAvatarImageView, LayoutBadgeImageView, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../common';
import { Button, Column, Flex, Grid, LayoutAvatarImageView, LayoutBadgeImageView, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../common';
import { useMessageEvent, useNotification } from '../../../hooks';
import { classNames } from '../../../layout';
export const GroupMembersView: FC<{}> = props =>
{
@ -166,8 +167,8 @@ export const GroupMembersView: FC<{}> = props =>
{ membersData.result.map((member, index) =>
{
return (
<Flex key={ index } alignItems="center" className="member-list-item bg-white rounded p-2" gap={ 2 } overflow="hidden">
<div className="avatar-head cursor-pointer" onClick={ () => GetUserProfile(member.id) }>
<Flex key={ index } alignItems="center" className="p-2 bg-white rounded member-list-item" gap={ 2 } overflow="hidden">
<div className="cursor-pointer avatar-head" onClick={ () => GetUserProfile(member.id) }>
<LayoutAvatarImageView direction={ 2 } figure={ member.figure } headOnly={ true } />
</div>
<Column grow gap={ 1 }>
@ -182,11 +183,11 @@ export const GroupMembersView: FC<{}> = props =>
</div> }
{ membersData.admin && (member.rank === GroupRank.REQUESTED) &&
<Flex alignItems="center">
<div className="nitro-friends-spritesheet icon-accept cursor-pointer" title={ LocalizeText('group.members.accept') } onClick={ event => acceptMembership(member) } />
<div className="cursor-pointer nitro-friends-spritesheet icon-accept" title={ LocalizeText('group.members.accept') } onClick={ event => acceptMembership(member) } />
</Flex> }
{ membersData.admin && (member.rank !== GroupRank.OWNER) && (member.id !== GetSessionDataManager().userId) &&
<Flex alignItems="center">
<div className="nitro-friends-spritesheet icon-deny cursor-pointer" title={ LocalizeText(member.rank === GroupRank.REQUESTED ? 'group.members.reject' : 'group.members.kick') } onClick={ event => removeMemberOrDeclineMembership(member) } />
<div className="cursor-pointer nitro-friends-spritesheet icon-deny" title={ LocalizeText(member.rank === GroupRank.REQUESTED ? 'group.members.reject' : 'group.members.kick') } onClick={ event => removeMemberOrDeclineMembership(member) } />
</Flex> }
</div>
</Flex>

View File

@ -1,8 +1,9 @@
import { GroupSaveColorsComposer } from '@nitrots/nitro-renderer';
import { Dispatch, FC, SetStateAction, useCallback, useEffect, useState } from 'react';
import { IGroupData, LocalizeText, SendMessageComposer } from '../../../../api';
import { AutoGrid, Column, Grid, Text, classNames } from '../../../../common';
import { AutoGrid, Column, Grid, Text } from '../../../../common';
import { useGroup } from '../../../../hooks';
import { classNames } from '../../../../layout';
interface GroupTabColorsViewProps
{
@ -99,7 +100,7 @@ export const GroupTabColorsView: FC<GroupTabColorsViewProps> = props =>
<Column gap={ 1 } size={ 2 }>
<Text bold>{ LocalizeText('group.edit.color.guild.color') }</Text>
{ groupData.groupColors && (groupData.groupColors.length > 0) &&
<div className="flex overflow-hidden rounded border">
<div className="flex overflow-hidden border rounded">
<div className="group-color-swatch" style={ { backgroundColor: '#' + getGroupColor(0) } } />
<div className="group-color-swatch" style={ { backgroundColor: '#' + getGroupColor(1) } } />
</div> }

View File

@ -1,8 +1,9 @@
import { GetSessionDataManager, GuideSessionGetRequesterRoomMessageComposer, GuideSessionInviteRequesterMessageComposer, GuideSessionMessageMessageComposer, GuideSessionRequesterRoomMessageEvent, GuideSessionResolvedMessageComposer } from '@nitrots/nitro-renderer';
import { FC, KeyboardEvent, useCallback, useEffect, useRef, useState } from 'react';
import { GuideToolMessageGroup, LocalizeText, SendMessageComposer, TryVisitRoom } from '../../../api';
import { Button, Column, Flex, LayoutAvatarImageView, Text, classNames } from '../../../common';
import { Button, Column, Flex, LayoutAvatarImageView, Text } from '../../../common';
import { useMessageEvent } from '../../../hooks';
import { classNames } from '../../../layout';
interface GuideToolOngoingViewProps
{
@ -72,7 +73,7 @@ export const GuideToolOngoingView: FC<GuideToolOngoingViewProps> = props =>
return (
<Column fullHeight>
<Flex alignItems="center" className="bg-muted p-2 rounded" gap={ 1 } justifyContent="between">
<Flex alignItems="center" className="p-2 rounded bg-muted" gap={ 1 } justifyContent="between">
{ isGuide &&
<div className="btn-group">
<Button onClick={ visit }>{ LocalizeText('guide.help.request.guide.ongoing.visit.button') }</Button>
@ -85,13 +86,13 @@ export const GuideToolOngoingView: FC<GuideToolOngoingViewProps> = props =>
</Column> }
<Button disabled variant="danger">{ LocalizeText('guide.help.common.report.link') }</Button>
</Flex>
<Column className="bg-muted rounded chat-messages p-2" gap={ 1 } overflow="hidden">
<Column className="p-2 rounded bg-muted chat-messages" gap={ 1 } overflow="hidden">
<Column overflow="auto">
{ messageGroups.map((group, index) =>
{
return (
<Flex key={ index } fullWidth gap={ 2 } justifyContent={ isOwnChat(group.userId) ? 'end' : 'start' }>
<div className="message-avatar flex-shrink-0">
<div className="flex-shrink-0 message-avatar">
{ (!isOwnChat(group.userId)) &&
<LayoutAvatarImageView direction={ 2 } figure={ userFigure } /> }
</div>
@ -103,7 +104,7 @@ export const GuideToolOngoingView: FC<GuideToolOngoingViewProps> = props =>
{ group.messages.map((chat, index) => <div key={ index } className={ classNames(chat.roomId ? 'text-break text-underline' : 'text-break', 'chat.roomId' && 'cursor-pointer') } onClick={ () => chat.roomId ? TryVisitRoom(chat.roomId) : null }>{ chat.message }</div>) }
</div>
{ (isOwnChat(group.userId)) &&
<div className="message-avatar flex-shrink-0">
<div className="flex-shrink-0 message-avatar">
<LayoutAvatarImageView direction={ 4 } figure={ GetSessionDataManager().figure } />
</div> }
</Flex>

View File

@ -140,7 +140,7 @@ export const HcCenterView: FC<{}> = props =>
</Button>
</Flex>
</div>
<div className="end-0 p-4 top-0 habbo-avatar position-absolute">
<div className="end-0 p-4 top-0 habbo-avatar absolute">
<LayoutAvatarImageView direction={ 4 } figure={ userFigure } scale={ 2 } />
</div>
</Flex>

View File

@ -90,12 +90,12 @@ export const HotelView: FC<{}> = props =>
</div>
</div>
</div>
<div className="background position-absolute" style={ (background && background.length) ? { backgroundImage: `url(${ background })` } : {} } />
<div className="sun position-absolute" style={ (sun && sun.length) ? { backgroundImage: `url(${ sun })` } : {} } />
<div className="drape position-absolute" style={ (drape && drape.length) ? { backgroundImage: `url(${ drape })` } : {} } />
<div className="left position-absolute" style={ (left && left.length) ? { backgroundImage: `url(${ left })` } : {} } />
<div className="right-repeat position-absolute" style={ (rightRepeat && rightRepeat.length) ? { backgroundImage: `url(${ rightRepeat })` } : {} } />
<div className="right position-absolute" style={ (right && right.length) ? { backgroundImage: `url(${ right })` } : {} } />
<div className="background absolute" style={ (background && background.length) ? { backgroundImage: `url(${ background })` } : {} } />
<div className="sun absolute" style={ (sun && sun.length) ? { backgroundImage: `url(${ sun })` } : {} } />
<div className="drape absolute" style={ (drape && drape.length) ? { backgroundImage: `url(${ drape })` } : {} } />
<div className="left absolute" style={ (left && left.length) ? { backgroundImage: `url(${ left })` } : {} } />
<div className="right-repeat absolute" style={ (rightRepeat && rightRepeat.length) ? { backgroundImage: `url(${ rightRepeat })` } : {} } />
<div className="right absolute" style={ (right && right.length) ? { backgroundImage: `url(${ right })` } : {} } />
{ GetConfigurationValue('hotelview')['show.avatar'] && (
<div className="avatar-image">
<LayoutAvatarImageView direction={ 2 } figure={ userFigure } />

View File

@ -33,9 +33,9 @@ export const BonusRareWidgetView: FC<BonusRareWidgetViewProps> = props =>
return (
<div className="bonus-rare widget flex">
{ productType }
<div className="bg-light-dark rounded overflow-hidden position-relative bonus-bar-container">
<div className="flex justify-center items-center size-full position-absolute small top-0">{ (totalCoinsForBonus - coinsStillRequiredToBuy) + '/' + totalCoinsForBonus }</div>
<div className="small bg-info rounded position-absolute top-0 h-100" style={ { width: ((totalCoinsForBonus - coinsStillRequiredToBuy) / totalCoinsForBonus) * 100 + '%' } }></div>
<div className="bg-light-dark rounded overflow-hidden relative bonus-bar-container">
<div className="flex justify-center items-center size-full absolute small top-0">{ (totalCoinsForBonus - coinsStillRequiredToBuy) + '/' + totalCoinsForBonus }</div>
<div className="small bg-info rounded absolute top-0 h-100" style={ { width: ((totalCoinsForBonus - coinsStillRequiredToBuy) / totalCoinsForBonus) * 100 + '%' } }></div>
</div>
</div>
);

View File

@ -1,8 +1,8 @@
import { MouseEventType } from '@nitrots/nitro-renderer';
import { FC, MouseEvent, useState } from 'react';
import { attemptItemPlacement, GroupItem } from '../../../../api';
import { LayoutGridItem } from '../../../../common';
import { GroupItem, attemptItemPlacement } from '../../../../api';
import { useInventoryFurni } from '../../../../hooks';
import { InfiniteGrid, classNames } from '../../../../layout';
export const InventoryFurnitureItemView: FC<{ groupItem: GroupItem }> = props =>
{
@ -34,5 +34,5 @@ export const InventoryFurnitureItemView: FC<{ groupItem: GroupItem }> = props =>
const count = groupItem.getUnlockedCount();
return <LayoutGridItem className={ !count ? 'opacity-0-5 ' : '' } itemActive={ (groupItem === selectedItem) } itemCount={ groupItem.getUnlockedCount() } itemImage={ groupItem.iconUrl } itemUniqueNumber={ groupItem.stuffData.uniqueNumber } itemUnseen={ groupItem.hasUnseenItems } onDoubleClick={ onMouseEvent } onMouseDown={ onMouseEvent } onMouseOut={ onMouseEvent } onMouseUp={ onMouseEvent } { ...rest } />;
return <InfiniteGrid.Item className={ classNames(!count && 'opacity-50') } itemActive={ (groupItem === selectedItem) } itemCount={ groupItem.getUnlockedCount() } itemImage={ groupItem.iconUrl } itemUniqueNumber={ groupItem.stuffData.uniqueNumber } itemUnseen={ groupItem.hasUnseenItems } onDoubleClick={ onMouseEvent } onMouseDown={ onMouseEvent } onMouseOut={ onMouseEvent } onMouseUp={ onMouseEvent } />
}

View File

@ -112,11 +112,11 @@ export const InventoryFurnitureView: FC<InventoryFurnitureViewProps> = props =>
if(!groupItems || !groupItems.length) return <InventoryCategoryEmptyView desc={ LocalizeText('inventory.empty.desc') } title={ LocalizeText('inventory.empty.title') } />;
return (
<div className="grid grid-cols-12 gap-2">
<div className="flex flex-col col-span-7 overflow-hidden">
<div className="grid h-full grid-cols-12 gap-2 overflow-hidden">
<div className="flex flex-col col-span-7 gap-1 overflow-hidden">
<InventoryFurnitureSearchView groupItems={ groupItems } setGroupItems={ setFilteredGroupItems } />
<InfiniteGrid<GroupItem>
columnCount={ 5 }
columnCount={ 6 }
itemRender={ item => <InventoryFurnitureItemView groupItem={ item } /> }
items={ filteredGroupItems } />
</div>

View File

@ -123,17 +123,17 @@ export const ModToolsView: FC<{}> = props =>
<NitroCardView className="nitro-mod-tools" theme="primary-slim" uniqueKey="mod-tools" windowPosition={ DraggableWindowPosition.TOP_LEFT } >
<NitroCardHeaderView headerText={ 'Mod Tools' } onCloseClick={ event => setIsVisible(false) } />
<NitroCardContentView className="text-black" gap={ 1 }>
<Button className="position-relative" disabled={ (currentRoomId <= 0) } gap={ 1 } onClick={ event => CreateLinkEvent(`mod-tools/toggle-room-info/${ currentRoomId }`) }>
<div className="icon icon-small-room position-absolute start-1"/> Room Tool
<Button className="relative" disabled={ (currentRoomId <= 0) } gap={ 1 } onClick={ event => CreateLinkEvent(`mod-tools/toggle-room-info/${ currentRoomId }`) }>
<div className="icon icon-small-room absolute start-1"/> Room Tool
</Button>
<Button className="position-relative" disabled={ (currentRoomId <= 0) } gap={ 1 } innerRef={ elementRef } onClick={ event => CreateLinkEvent(`mod-tools/toggle-room-chatlog/${ currentRoomId }`) }>
<div className="icon icon-chat-history position-absolute start-1"/> Chatlog Tool
<Button className="relative" disabled={ (currentRoomId <= 0) } gap={ 1 } innerRef={ elementRef } onClick={ event => CreateLinkEvent(`mod-tools/toggle-room-chatlog/${ currentRoomId }`) }>
<div className="icon icon-chat-history absolute start-1"/> Chatlog Tool
</Button>
<Button className="position-relative" disabled={ !selectedUser } gap={ 1 } onClick={ () => CreateLinkEvent(`mod-tools/toggle-user-info/${ selectedUser.userId }`) }>
<div className="icon icon-user position-absolute start-1"/> User: { selectedUser ? selectedUser.username : '' }
<Button className="relative" disabled={ !selectedUser } gap={ 1 } onClick={ () => CreateLinkEvent(`mod-tools/toggle-user-info/${ selectedUser.userId }`) }>
<div className="icon icon-user absolute start-1"/> User: { selectedUser ? selectedUser.username : '' }
</Button>
<Button className="position-relative" gap={ 1 } onClick={ () => setIsTicketsVisible(prevValue => !prevValue) }>
<div className="icon icon-tickets position-absolute start-1"/> Report Tool
<Button className="relative" gap={ 1 } onClick={ () => setIsTicketsVisible(prevValue => !prevValue) }>
<div className="icon icon-tickets absolute start-1"/> Report Tool
</Button>
</NitroCardContentView>
</NitroCardView> }

View File

@ -2,9 +2,10 @@ import { CreateLinkEvent, GetCustomRoomFilterMessageComposer, GetSessionDataMana
import { FC, useEffect, useState } from 'react';
import { FaLink } from 'react-icons/fa';
import { DispatchUiEvent, GetGroupInformation, LocalizeText, ReportType, SendMessageComposer } from '../../../api';
import { Button, Column, Flex, LayoutBadgeImageView, LayoutRoomThumbnailView, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text, UserProfileIconView, classNames } from '../../../common';
import { Button, Column, Flex, LayoutBadgeImageView, LayoutRoomThumbnailView, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text, UserProfileIconView } from '../../../common';
import { RoomWidgetThumbnailEvent } from '../../../events';
import { useHelp, useNavigator } from '../../../hooks';
import { classNames } from '../../../layout';
export class NavigatorRoomInfoViewProps
{
@ -106,7 +107,7 @@ export const NavigatorRoomInfoView: FC<NavigatorRoomInfoViewProps> = props =>
<>
<Flex gap={ 2 } overflow="hidden">
<LayoutRoomThumbnailView customUrl={ navigatorData.enteredGuestRoom.officialRoomPicRef } roomId={ navigatorData.enteredGuestRoom.roomId }>
{ hasPermission('settings') && <i className="icon icon-camera-small position-absolute b-0 r-0 m-1 cursor-pointer top-0" onClick={ () => processAction('open_room_thumbnail_camera') } /> }
{ hasPermission('settings') && <i className="top-0 m-1 cursor-pointer icon icon-camera-small absolute b-0 r-0" onClick={ () => processAction('open_room_thumbnail_camera') } /> }
</LayoutRoomThumbnailView>
<Column grow gap={ 1 } overflow="hidden">
<div className="flex gap-1">
@ -131,13 +132,13 @@ export const NavigatorRoomInfoView: FC<NavigatorRoomInfoViewProps> = props =>
<div className="flex items-center gap-1">
{ navigatorData.enteredGuestRoom.tags.map(tag =>
{
return <Text key={ tag } pointer className="bg-muted rounded p-1" onClick={ event => processAction('navigator_search_tag', tag) }>#{ tag }</Text>
return <Text key={ tag } pointer className="p-1 rounded bg-muted" onClick={ event => processAction('navigator_search_tag', tag) }>#{ tag }</Text>
}) }
</div> }
</Column>
<Column alignItems="center" gap={ 1 }>
{ hasPermission('settings') &&
<i className="icon icon-cog cursor-pointer" title={ LocalizeText('navigator.room.popup.info.room.settings') } onClick={ event => processAction('open_room_settings') } /> }
<i className="cursor-pointer icon icon-cog" title={ LocalizeText('navigator.room.popup.info.room.settings') } onClick={ event => processAction('open_room_settings') } /> }
<FaLink className="cursor-pointer fa-icon" title={ LocalizeText('navigator.embed.caption') } onClick={ event => CreateLinkEvent('navigator/toggle-room-link') } />
</Column>
</div>

View File

@ -44,9 +44,9 @@ export const NavigatorSearchResultItemInfoView: FC<{
<Flex gap={ 2 } overflow="hidden">
<LayoutRoomThumbnailView className="flex flex-col items-center mb-1 justify-content-end" customUrl={ roomData.officialRoomPicRef } roomId={ roomData.roomId }>
{ roomData.habboGroupId > 0 && (
<LayoutBadgeImageView badgeCode={ roomData.groupBadgeCode } className={ 'position-absolute top-0 start-0 m-1 ' } isGroup={ true }/>) }
<LayoutBadgeImageView badgeCode={ roomData.groupBadgeCode } className={ 'absolute top-0 start-0 m-1 ' } isGroup={ true }/>) }
{ roomData.doorMode !== RoomDataParser.OPEN_STATE && (
<i className={ 'position-absolute end-0 mb-1 me-1 icon icon-navigator-room-' + (roomData.doorMode === RoomDataParser.DOORBELL_STATE ? 'locked' : roomData.doorMode === RoomDataParser.PASSWORD_STATE ? 'password' : roomData.doorMode === RoomDataParser.INVISIBLE_STATE ? 'invisible' : '') }/> ) }
<i className={ 'absolute end-0 mb-1 me-1 icon icon-navigator-room-' + (roomData.doorMode === RoomDataParser.DOORBELL_STATE ? 'locked' : roomData.doorMode === RoomDataParser.PASSWORD_STATE ? 'password' : roomData.doorMode === RoomDataParser.INVISIBLE_STATE ? 'invisible' : '') }/> ) }
</LayoutRoomThumbnailView>
<div className="flex flex-col gap-1">
<Text bold truncate className="flex-grow-1" style={ { maxHeight: 13 } }>
@ -64,7 +64,7 @@ export const NavigatorSearchResultItemInfoView: FC<{
<Text className="flex-grow-1">
{ roomData.description }
</Text>
<Flex className={ 'badge p-1 position-absolute m-1 bottom-0 end-0 m-2 ' + getUserCounterColor() } gap={ 1 }>
<Flex className={ 'badge p-1 absolute m-1 bottom-0 end-0 m-2 ' + getUserCounterColor() } gap={ 1 }>
<FaUser className="fa-icon" />
{ roomData.userCount }
</Flex>

View File

@ -83,13 +83,13 @@ export const NavigatorSearchResultItemView: FC<NavigatorSearchResultItemViewProp
if(thumbnail) return (
<Column pointer alignItems="center" className="navigator-item p-1 bg-light rounded-3 small mb-1 flex-col border border-muted" gap={ 0 } overflow="hidden" onClick={ visitRoom } { ...rest }>
<LayoutRoomThumbnailView className="flex flex-col items-center justify-content-end mb-1" customUrl={ roomData.officialRoomPicRef } roomId={ roomData.roomId }>
{ roomData.habboGroupId > 0 && <LayoutBadgeImageView badgeCode={ roomData.groupBadgeCode } className={ 'position-absolute top-0 start-0 m-1' } isGroup={ true } /> }
<Flex center className={ 'badge p-1 position-absolute m-1 ' + getUserCounterColor() } gap={ 1 }>
{ roomData.habboGroupId > 0 && <LayoutBadgeImageView badgeCode={ roomData.groupBadgeCode } className={ 'absolute top-0 start-0 m-1' } isGroup={ true } /> }
<Flex center className={ 'badge p-1 absolute m-1 ' + getUserCounterColor() } gap={ 1 }>
<FaUser className="fa-icon" />
{ roomData.userCount }
</Flex>
{ (roomData.doorMode !== RoomDataParser.OPEN_STATE) &&
<i className={ ('position-absolute end-0 mb-1 me-1 icon icon-navigator-room-' + ((roomData.doorMode === RoomDataParser.DOORBELL_STATE) ? 'locked' : (roomData.doorMode === RoomDataParser.PASSWORD_STATE) ? 'password' : (roomData.doorMode === RoomDataParser.INVISIBLE_STATE) ? 'invisible' : '')) } /> }
<i className={ ('absolute end-0 mb-1 me-1 icon icon-navigator-room-' + ((roomData.doorMode === RoomDataParser.DOORBELL_STATE) ? 'locked' : (roomData.doorMode === RoomDataParser.PASSWORD_STATE) ? 'password' : (roomData.doorMode === RoomDataParser.INVISIBLE_STATE) ? 'invisible' : '')) } /> }
</LayoutRoomThumbnailView>
<Flex className="w-100">
<Text truncate className="flex-grow-1">{ roomData.roomName }</Text>

View File

@ -1,8 +1,8 @@
import { GetRenderer } from '@nitrots/nitro-renderer';
import { FC, useEffect, useRef } from 'react';
import { DispatchMouseEvent, DispatchTouchEvent } from '../../api';
import { classNames } from '../../common';
import { useRoom } from '../../hooks';
import { classNames } from '../../layout';
import { RoomSpectatorView } from './spectator/RoomSpectatorView';
import { RoomWidgetsView } from './widgets/RoomWidgetsView';

View File

@ -154,11 +154,13 @@ export const RoomWidgetsView: FC<{}> = props =>
return (
<>
<div className="absolute top-0 left-0 pointer-events-none size-full">
<FurnitureWidgetsView />
</div>
<AvatarInfoWidgetView />
<ChatWidgetView />
<ChatInputView />
<DoorbellWidgetView />
<FurnitureWidgetsView />
<RoomToolsWidgetView />
<RoomFilterWordsWidgetView />
<RoomThumbnailWidgetView />

View File

@ -340,11 +340,11 @@ export const InfoStandWidgetFurniView: FC<InfoStandWidgetFurniViewProps> = props
<div className="flex flex-col gap-1">
<Flex gap={ 1 } position="relative">
{ avatarInfo.stuffData.isUnique &&
<div className="position-absolute end-0">
<div className="absolute end-0">
<LayoutLimitedEditionCompactPlateView uniqueNumber={ avatarInfo.stuffData.uniqueNumber } uniqueSeries={ avatarInfo.stuffData.uniqueSeries } />
</div> }
{ (avatarInfo.stuffData.rarityLevel > -1) &&
<div className="position-absolute end-0">
<div className="absolute end-0">
<LayoutRarityLevelView level={ avatarInfo.stuffData.rarityLevel } />
</div> }
<Flex center fullWidth>

View File

@ -98,8 +98,8 @@ export const InfoStandWidgetPetView: FC<InfoStandWidgetPetViewProps> = props =>
</Column> }
<Column alignItems="center" gap={ 1 }>
<Text small truncate variant="white">{ LocalizeText('infostand.pet.text.wellbeing') }</Text>
<div className="bg-light-dark rounded position-relative overflow-hidden w-100">
<div className="flex justify-center items-center size-full position-absolute">
<div className="bg-light-dark rounded relative overflow-hidden w-100">
<div className="flex justify-center items-center size-full absolute">
<Text small variant="white">{ avatarInfo.dead ? '00:00:00' : ConvertSeconds((remainingTimeToLive == 0 ? avatarInfo.remainingTimeToLive : remainingTimeToLive)).split(':')[1] + ':' + ConvertSeconds((remainingTimeToLive == null || remainingTimeToLive == undefined ? 0 : remainingTimeToLive)).split(':')[2] + ':' + ConvertSeconds((remainingTimeToLive == null || remainingTimeToLive == undefined ? 0 : remainingTimeToLive)).split(':')[3] }</Text>
</div>
<div className="bg-success rounded pet-stats" style={ { width: avatarInfo.dead ? '0' : Math.round((avatarInfo.maximumTimeToLive * 100) / (remainingTimeToLive)).toString() } } />
@ -132,8 +132,8 @@ export const InfoStandWidgetPetView: FC<InfoStandWidgetPetViewProps> = props =>
<Text center small wrap variant="white">{ LocalizeText('pet.level', [ 'level', 'maxlevel' ], [ avatarInfo.level.toString(), avatarInfo.maximumLevel.toString() ]) }</Text>
<Column alignItems="center" gap={ 1 }>
<Text small truncate variant="white">{ LocalizeText('infostand.pet.text.happiness') }</Text>
<div className="bg-light-dark rounded position-relative overflow-hidden w-100">
<div className="flex justify-center items-center size-full position-absolute">
<div className="bg-light-dark rounded relative overflow-hidden w-100">
<div className="flex justify-center items-center size-full absolute">
<Text small variant="white">{ avatarInfo.happyness + '/' + avatarInfo.maximumHappyness }</Text>
</div>
<div className="bg-info rounded pet-stats" style={ { width: (avatarInfo.happyness / avatarInfo.maximumHappyness) * 100 + '%' } } />
@ -141,8 +141,8 @@ export const InfoStandWidgetPetView: FC<InfoStandWidgetPetViewProps> = props =>
</Column>
<Column alignItems="center" gap={ 1 }>
<Text small truncate variant="white">{ LocalizeText('infostand.pet.text.experience') }</Text>
<div className="bg-light-dark rounded position-relative overflow-hidden w-100">
<div className="flex justify-center items-center size-full position-absolute">
<div className="bg-light-dark rounded relative overflow-hidden w-100">
<div className="flex justify-center items-center size-full absolute">
<Text small variant="white">{ avatarInfo.experience + '/' + avatarInfo.levelExperienceGoal }</Text>
</div>
<div className="bg-purple rounded pet-stats" style={ { width: (avatarInfo.experience / avatarInfo.levelExperienceGoal) * 100 + '%' } } />
@ -150,8 +150,8 @@ export const InfoStandWidgetPetView: FC<InfoStandWidgetPetViewProps> = props =>
</Column>
<Column alignItems="center" gap={ 1 }>
<Text small truncate variant="white">{ LocalizeText('infostand.pet.text.energy') }</Text>
<div className="bg-light-dark rounded position-relative overflow-hidden w-100">
<div className="flex justify-center items-center size-full position-absolute">
<div className="bg-light-dark rounded relative overflow-hidden w-100">
<div className="flex justify-center items-center size-full absolute">
<Text small variant="white">{ avatarInfo.energy + '/' + avatarInfo.maximumEnergy }</Text>
</div>
<div className="bg-success rounded pet-stats" style={ { width: (avatarInfo.energy / avatarInfo.maximumEnergy) * 100 + '%' } } />

View File

@ -1,7 +1,8 @@
import { GetSessionDataManager } from '@nitrots/nitro-renderer';
import { FC, useEffect, useMemo, useState } from 'react';
import { LocalizeText, RoomObjectItem } from '../../../../api';
import { Flex, InfiniteScroll, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text, classNames } from '../../../../common';
import { Flex, InfiniteScroll, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../../common';
import { classNames } from '../../../../layout';
interface ChooserWidgetViewProps
{

View File

@ -94,7 +94,7 @@ export const ContextMenuView: FC<ContextMenuViewProps> = props =>
const getClassNames = useMemo(() =>
{
const newClassNames: string[] = [ 'nitro-context-menu', 'position-absolute' ];
const newClassNames: string[] = [ 'nitro-context-menu', 'absolute' ];
if (isCollapsed) newClassNames.push('menu-hidden');

View File

@ -2,8 +2,9 @@ import { RoomEngineTriggerWidgetEvent } from '@nitrots/nitro-renderer';
import { FC, useEffect, useMemo, useState } from 'react';
import ReactSlider from 'react-slider';
import { ColorUtils, FurnitureDimmerUtilities, GetConfigurationValue, LocalizeText } from '../../../../api';
import { Button, Column, Grid, NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView, Text, classNames } from '../../../../common';
import { Button, Column, Grid, NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView, Text } from '../../../../common';
import { useFurnitureDimmerWidget, useNitroEvent } from '../../../../hooks';
import { classNames } from '../../../../layout';
export const FurnitureDimmerView: FC<{}> = props =>
{
@ -41,7 +42,7 @@ export const FurnitureDimmerView: FC<{}> = props =>
{ (dimmerState === 0) &&
<Column alignItems="center">
<div className="dimmer-banner" />
<Text center className="bg-muted rounded p-1">{ LocalizeText('widget.dimmer.info.off') }</Text>
<Text center className="p-1 rounded bg-muted">{ LocalizeText('widget.dimmer.info.off') }</Text>
<Button fullWidth variant="success" onClick={ () => FurnitureDimmerUtilities.changeState() }>{ LocalizeText('widget.dimmer.button.on') }</Button>
</Column> }
{ (dimmerState === 1) &&

View File

@ -86,10 +86,10 @@ export const FurnitureMannequinView: FC<{}> = props =>
<NitroCardContentView center>
<div className="flex w-100 gap-2 overflow-hidden">
<div className="flex flex-col">
<div className="position-relative mannequin-preview">
<div className="relative mannequin-preview">
<LayoutAvatarImageView direction={ 2 } figure={ renderedFigure } position="absolute" />
{ (clubLevel > 0) &&
<LayoutCurrencyIcon className="position-absolute end-2 bottom-2" type="hc" /> }
<LayoutCurrencyIcon className="absolute end-2 bottom-2" type="hc" /> }
</div>
</div>
<Column grow justifyContent="between" overflow="auto">

View File

@ -22,7 +22,7 @@ import { FurniturePlaylistEditorWidgetView } from './playlist-editor/FurniturePl
export const FurnitureWidgetsView: FC<{}> = props =>
{
return (
<div className="position-absolute size-full nitro-room-widgets top-0 start-0">
<>
<FurnitureBackgroundColorView />
<FurnitureBadgeDisplayView />
<FurnitureCraftingView />
@ -42,6 +42,6 @@ export const FurnitureWidgetsView: FC<{}> = props =>
<FurnitureTrophyView />
<FurnitureContextMenuView />
<FurnitureYoutubeDisplayView />
</div>
</>
);
}

View File

@ -16,10 +16,10 @@ export const FurniturePlaylistEditorWidgetView: FC<{}> = props =>
<NitroCardHeaderView headerText={ LocalizeText('playlist.editor.title') } onCloseClick={ onClose } />
<NitroCardContentView>
<div className="flex flex-row gap-1 h-100">
<div className="w-50 position-relative overflow-hidden h-100 rounded flex flex-col">
<div className="w-50 relative overflow-hidden h-100 rounded flex flex-col">
<DiskInventoryView addToPlaylist={ addToPlaylist } diskInventory={ diskInventory } />
</div>
<div className="w-50 position-relative overflow-hidden h-100 rounded flex flex-col">
<div className="w-50 relative overflow-hidden h-100 rounded flex flex-col">
<SongPlaylistView currentPlayingIndex={ currentPlayingIndex } furniId={ objectId } playlist={ playlist } removeFromPlaylist={ removeFromPlaylist } togglePlayPause={ togglePlayPause }/>
</div>
</div>

View File

@ -57,5 +57,5 @@ export const ObjectLocationView: FC<ObjectLocationViewProps> = props =>
}
}, [ objectId, category, noFollow ]);
return <div ref={ elementRef } className="object-location position-absolute" style={ { left: pos.x, top: pos.y, visibility: ((pos.x + (elementRef.current ? elementRef.current.offsetWidth : 0)) > -1) ? 'visible' : 'hidden' } } { ...rest } />;
return <div ref={ elementRef } className="object-location absolute" style={ { left: pos.x, top: pos.y, visibility: ((pos.x + (elementRef.current ? elementRef.current.offsetWidth : 0)) > -1) ? 'visible' : 'hidden' } } { ...rest } />;
}

View File

@ -1,8 +1,9 @@
import { UpdateRoomFilterMessageComposer } from '@nitrots/nitro-renderer';
import { FC, useState } from 'react';
import { LocalizeText, SendMessageComposer } from '../../../../api';
import { Button, Column, Flex, Grid, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text, classNames } from '../../../../common';
import { Button, Column, Flex, Grid, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../../common';
import { useFilterWordsWidget, useNavigator } from '../../../../hooks';
import { classNames } from '../../../../layout';
export const RoomFilterWordsWidgetView: FC<{}> = props =>
{
@ -51,7 +52,7 @@ export const RoomFilterWordsWidgetView: FC<{}> = props =>
<NitroCardView className="nitro-guide-tool no-resize" theme="primary-slim">
<NitroCardHeaderView headerText={ LocalizeText('navigator.roomsettings.roomfilter') } onCloseClick={ () => onClose() } />
<NitroCardContentView className="text-black">
<Grid className="flex items-center justify-content-end gap-2">
<Grid className="flex items-center gap-2 justify-content-end">
<input className="form-control form-control-sm" maxLength={ 255 } type="text" value={ word } onChange={ event => onTyping(event.target.value) } />
<Button onClick={ () => processAction(true) }>{ LocalizeText('navigator.roomsettings.roomfilter.addword') }</Button>
</Grid>
@ -65,7 +66,7 @@ export const RoomFilterWordsWidgetView: FC<{}> = props =>
)
}) }
</Column>
<Grid className="flex items-center justify-content-end gap-2">
<Grid className="flex items-center gap-2 justify-content-end">
<Button disabled={ wordsFilter.length === 0 || !isSelectingWord } variant="danger" onClick={ () => processAction(false) }>{ LocalizeText('navigator.roomsettings.roomfilter.removeword') }</Button>
</Grid>
</NitroCardContentView>

View File

@ -18,8 +18,8 @@ export const RoomPromoteOtherEventWidgetView: FC<RoomPromoteOtherEventWidgetView
</Flex>
<br /><br />
<Column alignItems="center" gap={ 1 }>
<div className="bg-light-dark rounded position-relative overflow-hidden w-100">
<div className="flex justify-center items-center size-full position-absolute">
<div className="bg-light-dark rounded relative overflow-hidden w-100">
<div className="flex justify-center items-center size-full absolute">
<Text center variant="white">{ LocalizeText('navigator.eventinprogress') }</Text>
</div>
<Text>&nbsp;</Text>

View File

@ -1,8 +1,9 @@
import { CreateLinkEvent, GetGuestRoomResultEvent, GetRoomEngine, NavigatorSearchComposer, RateFlatMessageComposer } from '@nitrots/nitro-renderer';
import { FC, useEffect, useState } from 'react';
import { LocalizeText, SendMessageComposer } from '../../../../api';
import { Text, TransitionAnimation, TransitionAnimationTypes, classNames } from '../../../../common';
import { Text, TransitionAnimation, TransitionAnimationTypes } from '../../../../common';
import { useMessageEvent, useNavigator, useRoom } from '../../../../hooks';
import { classNames } from '../../../../layout';
export const RoomToolsWidgetView: FC<{}> = props =>
{
@ -72,24 +73,24 @@ export const RoomToolsWidgetView: FC<{}> = props =>
return (
<div className="flex gap-2 nitro-room-tools-container">
<div className="flex flex-col justify-center items-center nitro-room-tools p-2">
<div className="icon icon-cog cursor-pointer" title={ LocalizeText('room.settings.button.text') } onClick={ () => handleToolClick('settings') } />
<div className="flex flex-col items-center justify-center p-2 nitro-room-tools">
<div className="cursor-pointer icon icon-cog" title={ LocalizeText('room.settings.button.text') } onClick={ () => handleToolClick('settings') } />
<div className={ classNames('cursor-pointer', 'icon', (!isZoomedIn && 'icon-zoom-less'), (isZoomedIn && 'icon-zoom-more')) } title={ LocalizeText('room.zoom.button.text') } onClick={ () => handleToolClick('zoom') } />
<div className="icon icon-chat-history cursor-pointer" title={ LocalizeText('room.chathistory.button.text') } onClick={ () => handleToolClick('chat_history') } />
<div className="cursor-pointer icon icon-chat-history" title={ LocalizeText('room.chathistory.button.text') } onClick={ () => handleToolClick('chat_history') } />
{ navigatorData.canRate &&
<div className="icon icon-like-room cursor-pointer" title={ LocalizeText('room.like.button.text') } onClick={ () => handleToolClick('like_room') } /> }
<div className="cursor-pointer icon icon-like-room" title={ LocalizeText('room.like.button.text') } onClick={ () => handleToolClick('like_room') } /> }
</div>
<div className="flex flex-col justify-center">
<TransitionAnimation inProp={ isOpen } timeout={ 300 } type={ TransitionAnimationTypes.SLIDE_LEFT }>
<div className="flex flex-col justify-center items-center">
<div className="flex flex-col nitro-room-tools-info rounded py-2 px-3">
<div className="flex flex-col items-center justify-center">
<div className="flex flex-col px-3 py-2 rounded nitro-room-tools-info">
<div className="flex flex-col gap-1">
<Text wrap fontSize={ 4 } variant="white">{ roomName }</Text>
<Text fontSize={ 5 } variant="muted">{ roomOwner }</Text>
</div>
{ roomTags && roomTags.length > 0 &&
<div className="flex gap-2">
{ roomTags.map((tag, index) => <Text key={ index } pointer small className="rounded bg-primary p-1" variant="white" onClick={ () => handleToolClick('navigator_search_tag', tag) }>#{ tag }</Text>) }
{ roomTags.map((tag, index) => <Text key={ index } pointer small className="p-1 rounded bg-primary" variant="white" onClick={ () => handleToolClick('navigator_search_tag', tag) }>#{ tag }</Text>) }
</div> }
</div>
</div>

View File

@ -1,5 +1,5 @@
import { DetailedHTMLProps, forwardRef, HTMLAttributes, PropsWithChildren } from 'react';
import { classNames } from '../../common';
import { classNames } from '../../layout';
export const ToolbarItemView = forwardRef<HTMLDivElement, PropsWithChildren<{
icon: string;

View File

@ -1,8 +1,9 @@
import { CreateLinkEvent, Dispose, DropBounce, EaseOut, GetSessionDataManager, JumpBy, Motions, NitroToolbarAnimateIconEvent, PerkAllowancesMessageEvent, PerkEnum, Queue, Wait } from '@nitrots/nitro-renderer';
import { FC, useState } from 'react';
import { GetConfigurationValue, MessengerIconState, OpenMessengerChat, VisitDesktop } from '../../api';
import { LayoutAvatarImageView, LayoutItemCountView, TransitionAnimation, TransitionAnimationTypes, classNames } from '../../common';
import { LayoutAvatarImageView, LayoutItemCountView, TransitionAnimation, TransitionAnimationTypes } from '../../common';
import { useAchievements, useFriends, useInventoryUnseenTracker, useMessageEvent, useMessenger, useNitroEvent, useSessionInfo } from '../../hooks';
import { classNames } from '../../layout';
import { ToolbarItemView } from './ToolbarItemView';
import { ToolbarMeView } from './ToolbarMeView';

View File

@ -74,7 +74,7 @@ export const GroupsContainerView: FC<GroupsContainerViewProps> = props =>
return (
<LayoutGridItem key={ index } className="p-1" itemActive={ (selectedGroupId === group.groupId) } overflow="unset" onClick={ () => setSelectedGroupId(group.groupId) }>
{ itsMe &&
<i className={ 'position-absolute end-0 top-0 z-index-1 icon icon-group-' + (group.favourite ? 'favorite' : 'not-favorite') } onClick={ () => ToggleFavoriteGroup(group) } /> }
<i className={ 'absolute end-0 top-0 z-index-1 icon icon-group-' + (group.favourite ? 'favorite' : 'not-favorite') } onClick={ () => ToggleFavoriteGroup(group) } /> }
<LayoutBadgeImageView badgeCode={ group.badgeCode } isGroup={ true } />
</LayoutGridItem>
)

View File

@ -2,8 +2,9 @@ import { AddLinkEventTracker, ILinkEventTracker, NitroSettingsEvent, RemoveLinkE
import { FC, useEffect, useState } from 'react';
import { FaVolumeDown, FaVolumeMute, FaVolumeUp } from 'react-icons/fa';
import { DispatchMainEvent, DispatchUiEvent, LocalizeText, SendMessageComposer } from '../../api';
import { NitroCardContentView, NitroCardHeaderView, NitroCardView, Text, classNames } from '../../common';
import { NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../common';
import { useCatalogPlaceMultipleItems, useCatalogSkipPurchaseConfirmation, useMessageEvent } from '../../hooks';
import { classNames } from '../../layout';
export const UserSettingsView: FC<{}> = props =>
{

View File

@ -22,12 +22,20 @@ body {
width: 0.25rem;
}
::-webkit-scrollbar-track {
@apply rounded-md;
}
::-webkit-scrollbar-thumb {
@apply rounded-md;
}
::-webkit-scrollbar:horizontal {
height: 0.25rem;
@apply h-1;
}
::-webkit-scrollbar:not(:horizontal) {
width: 0.25rem;
@apply h-1;
}
::-webkit-scrollbar-track:horizontal {
@ -690,5 +698,14 @@ body {
width: 18px;
height: 19px;
}
&.icon-loading {
background-image: url("@/assets/images/ui/loading_icon.png");
&.with-size {
width: 16px;
height: 16px;
}
}
}
}

View File

@ -1,5 +1,7 @@
import { useVirtualizer } from '@tanstack/react-virtual';
import { Fragment, ReactElement, useEffect, useRef } from 'react';
import { DetailedHTMLProps, Fragment, HTMLAttributes, ReactElement, forwardRef, useEffect, useRef, useState } from 'react';
import { classNames } from './classNames';
import { styleNames } from './styleNames';
type Props<T> = {
items: T[];
@ -9,7 +11,7 @@ type Props<T> = {
itemRender?: (item: T, index?: number) => ReactElement;
}
export const InfiniteGrid = <T,>(props: Props<T>) =>
const InfiniteGridRoot = <T,>(props: Props<T>) =>
{
const { items = [], columnCount = 4, overscan = 5, estimateSize = 45, itemRender = null } = props;
const parentRef = useRef<HTMLDivElement>(null);
@ -21,6 +23,29 @@ export const InfiniteGrid = <T,>(props: Props<T>) =>
estimateSize: () => estimateSize
});
useEffect(() =>
{
const element = parentRef.current;
if(!element || !items) return;
const checkAndApplyPadding = () =>
{
if(!element) return;
element.style.paddingRight = (element.scrollHeight > element.clientHeight) ? '0.25rem' : '0';
}
checkAndApplyPadding();
window.addEventListener('resize', checkAndApplyPadding);
return () =>
{
window.removeEventListener('resize', checkAndApplyPadding);
}
}, [ items ]);
useEffect(() =>
{
if(!items || !items.length) return;
@ -31,23 +56,22 @@ export const InfiniteGrid = <T,>(props: Props<T>) =>
const virtualItems = virtualizer.getVirtualItems();
return (
<div ref={ parentRef } className="size-full position-relative" style={ { overflowY: 'auto', height: virtualizer.getTotalSize() } }>
<div
ref={ parentRef }
className="overflow-y-auto size-full">
<div
className="flex flex-col w-full gap-1"
className="flex flex-col w-full *:pb-1 relative"
style={ {
transform: `translateY(${ virtualItems[0]?.start ?? 0 }px)`
height: virtualizer.getTotalSize()
} }>
{ virtualItems.map(virtualRow => (
<div
key={ virtualRow.key + 'a' }
ref={ virtualizer.measureElement }
className="grid grid-cols-12 gap-2 "
className={ `grid grid-cols-${ columnCount } gap-1 absolute top-0 left-0 h-[45px] last:pb-0 w-full` }
data-index={ virtualRow.index }
style={ {
display: 'grid',
gap: '0.25rem',
minHeight: virtualRow.index === 0 ? estimateSize : virtualRow.size,
gridTemplateColumns: `repeat(${ columnCount }, 1fr)`
transform: `translateY(${ virtualRow.start }px)`
} }>
{ Array.from(Array(columnCount)).map((e,i) =>
{
@ -68,3 +92,73 @@ export const InfiniteGrid = <T,>(props: Props<T>) =>
</div>
);
}
const InfiniteGridItem = forwardRef<HTMLDivElement, {
itemImage?: string;
itemColor?: string;
itemActive?: boolean;
itemCount?: number;
itemCountMinimum?: number;
itemUniqueSoldout?: boolean;
itemUniqueNumber?: number;
itemUnseen?: boolean;
itemHighlight?: boolean;
disabled?: boolean;
} & DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>>((props, ref) =>
{
const { itemImage = undefined, itemColor = undefined, itemActive = false, itemCount = 1, itemCountMinimum = 1, itemUniqueSoldout = false, itemUniqueNumber = -2, itemUnseen = false, itemHighlight = false, disabled = false, className = null, style = {}, children = null, ...rest } = props;
const [ backgroundImageUrl, setBackgroundImageUrl ] = useState<string>(null);
const disposed = useRef<boolean>(false);
useEffect(() =>
{
if(!itemImage || !itemImage.length) return;
const image = new Image();
image.onload = () =>
{
if(disposed.current) return;
setBackgroundImageUrl(image.src);
}
image.src = itemImage;
}, [ itemImage ]);
useEffect(() =>
{
disposed.current = false;
return () =>
{
disposed.current = true;
}
}, []);
return (
<div
ref={ ref }
className={ classNames(
'flex flex-col items-center justify-center cursor-pointer overflow-hidden relative bg-center bg-no-repeat w-full rounded-md border-2',
(!backgroundImageUrl || !backgroundImageUrl.length) && 'nitro-icon icon-loading',
itemActive ? 'border-card-grid-item-active bg-card-grid-item-active' : 'border-card-grid-item-border bg-card-grid-item',
className
) }
style={ styleNames(
backgroundImageUrl && backgroundImageUrl.length && !(itemUniqueSoldout || (itemUniqueNumber > 0)) && {
backgroundImage: `url(${ backgroundImageUrl })`
},
style
) }
{ ...rest }>
{ children }
</div>
);
});
InfiniteGridItem.displayName = 'InfiniteGridItem';
export const InfiniteGrid = Object.assign(InfiniteGridRoot, {
Item: InfiniteGridItem
});

View File

@ -1,5 +1,6 @@
import { DetailedHTMLProps, forwardRef, HTMLAttributes, MouseEvent, PropsWithChildren } from 'react';
import { classNames, DraggableWindow, DraggableWindowPosition, DraggableWindowProps } from '../common';
import { DraggableWindow, DraggableWindowPosition, DraggableWindowProps } from '../common';
import { classNames } from './classNames';
import { NitroItemCountBadge } from './NitroItemCountBadge';
const NitroCardRoot = forwardRef<HTMLDivElement, PropsWithChildren<{
@ -57,7 +58,7 @@ const NitroCardContent = forwardRef<HTMLDivElement, {
<div
ref={ ref }
className={ classNames(
'overflow-auto bg-card-content-area p-2 h-full',
'flex flex-col overflow-auto bg-card-content-area p-2 h-full',
className
) }
{ ...rest }>

View File

@ -1,5 +1,5 @@
import { DetailedHTMLProps, forwardRef, HTMLAttributes, PropsWithChildren } from 'react';
import { classNames } from '../common';
import { classNames } from './classNames';
const classes = {
base: 'top-2 right-2 py-0.5 px-[3px] z-[1] rounded border',

View File

@ -1,3 +1,5 @@
export * from './InfiniteGrid';
export * from './NitroCard';
export * from './NitroItemCountBadge';
export * from './classNames';
export * from './styleNames';

8
src/layout/styleNames.ts Normal file
View File

@ -0,0 +1,8 @@
export const styleNames = (...styles: object[]) =>
{
let mergedStyle = {};
styles.filter(Boolean).forEach(style => mergedStyle = { ...mergedStyle, ...style });
return mergedStyle;
}

View File

@ -10,7 +10,11 @@ const colors = {
'card-border': '#283F5D',
'card-tab-item': '#B6BEC5',
'card-tab-item-active': '#DFDFDF',
'card-content-area': '#DFDFDF'
'card-content-area': '#DFDFDF',
'card-grid-item': '#CDD3D9',
'card-grid-item-active': '#ECECEC',
'card-grid-item-border': '#B6BEC5',
'card-grid-item-border-active': '#FFFFFF',
};
const boxShadow = {
@ -39,6 +43,20 @@ module.exports = {
}
},
},
safelist: [
'grid-cols-1',
'grid-cols-2',
'grid-cols-3',
'grid-cols-4',
'grid-cols-5',
'grid-cols-6',
'grid-cols-7',
'grid-cols-8',
'grid-cols-9',
'grid-cols-10',
'grid-cols-11',
'grid-cols-12'
],
darkMode: 'class',
plugins: [
require('@tailwindcss/forms'),