fix(client): catalogo mobile, BC/navigator/profilo/amici

- Catalogo Hippiehotel responsive su mobile (finestra 96vw/72vh, rail
  tappabile, sotto-pannelli ristretti); duckie ripristinato come entita
  separata (revert modifiche scrollbar sul suo CSS)
- BC e catalogo normale seguono entrambi il toggle tema; il duckie non
  duplica piu il logo nelle pagine info_duckets
- Navigator: highlight della tab segue currentTabCode (era bloccato su
  Pubblici mentre il contenuto cambiava)
- Inventario: link inventory/show/<tab> per deep-link a una scheda
- User Profile: avatar visibile e allineato a bg/stand, finestra piu
  grande, bordi puliti, Created/Last login mostrano il valore, bottoni
  Change Looks/Rooms/Change Badges/Add friends/Achievement funzionanti
- Amici: header non piu sovradimensionati e teste avatar inquadrate
  (regole flat: quelle annidate .nitro-friends{&...} non si applicavano)
This commit is contained in:
medievalshell
2026-05-30 05:49:04 +02:00
parent d79bdd33e1
commit f7e78674c6
11 changed files with 125 additions and 65 deletions
@@ -252,7 +252,8 @@ const CatalogClassicViewInner: FC<{}> = () =>
<div className="nitro-catalog-classic-layout-header-shell">
<CatalogBreadcrumbView />
<div className="nitro-catalog-classic-layout-hero">
{ !!currentPage?.localization?.getImage(0) && <img src={ currentPage.localization.getImage(0) } /> }
{ /* info_duckets renders its own logo in the body (BcInfoView) — don't duplicate it in the hero */ }
{ (currentPage?.layoutCode !== 'info_duckets') && !!currentPage?.localization?.getImage(0) && <img src={ currentPage.localization.getImage(0) } /> }
</div>
</div>
<div className="nitro-catalog-classic-layout-container">
+8 -4
View File
@@ -37,6 +37,10 @@ const CatalogModernViewInner: FC<{}> = () =>
const buildersClubHeaderStyle = (currentType === CatalogType.BUILDER)
? { borderColor: '#d79d2e', borderBottomColor: '#000', background: 'linear-gradient(180deg, #d89f2d 0%, #c68515 100%)' }
: undefined;
// Desktop = fixed 780x520. On mobile the window clamps below the viewport so
// it reads as a dialog (with margins) instead of filling the whole phone
// screen — applies to both the normal catalog and the Builders Club.
const catalogCardSize = 'w-[780px] h-[520px] max-w-[96vw] max-h-[72vh] sm:max-w-[100vw] sm:max-h-[92vh]';
useEffect(() =>
{
@@ -122,7 +126,7 @@ const CatalogModernViewInner: FC<{}> = () =>
return (
<>
{ isVisible &&
<NitroCardView className="nitro-catalog w-[780px] h-[520px]" uniqueKey="catalog">
<NitroCardView className={ `nitro-catalog ${ catalogCardSize }` } uniqueKey="catalog">
<NitroCardHeaderView className={ currentType === CatalogType.BUILDER ? 'builders-club-card-header' : '' } headerText={ LocalizeText('catalog.title') } onCloseClick={ () => setIsVisible(false) } style={ buildersClubHeaderStyle } />
<NitroCardContentView classNames={ [ 'p-0!', 'overflow-hidden!' ] }>
{ /* Admin banner */ }
@@ -141,7 +145,7 @@ const CatalogModernViewInner: FC<{}> = () =>
<CatalogBuildersClubStatusView />
<div className="flex min-h-0 flex-1">
{ /* === LEFT SIDEBAR === */ }
<div className="group/rail flex flex-col w-[52px] hover:w-[175px] min-w-[52px] bg-card-grid-item border-r-2 border-card-grid-item-border py-1.5 gap-px overflow-y-auto overflow-x-hidden transition-[width] duration-200 ease-in-out">
<div className="group/rail flex flex-col w-[52px] sm:hover:w-[175px] min-w-[52px] bg-card-grid-item border-r-2 border-card-grid-item-border py-1.5 gap-px overflow-y-auto overflow-x-hidden transition-[width] duration-200 ease-in-out [scrollbar-width:none] [&::-webkit-scrollbar]:hidden">
{ /* Favorites toggle */ }
<div
@@ -279,7 +283,7 @@ const CatalogModernViewInner: FC<{}> = () =>
: <span className="text-muted">{ LocalizeText('catalog.title') }</span> }
</div>
<div className="w-[180px] shrink-0">
<div className="w-[110px] sm:w-[180px] shrink-0">
<CatalogSearchView />
</div>
@@ -301,7 +305,7 @@ const CatalogModernViewInner: FC<{}> = () =>
</div>
: <>
{ !navigationHidden && activeNodes && activeNodes.length > 0 &&
<div className="w-[170px] min-w-[170px] border-r-2 border-card-grid-item-border bg-card-grid-item overflow-y-auto py-1">
<div className="w-[120px] min-w-[120px] sm:w-[170px] sm: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 nitro-card-content-shell">
+1
View File
@@ -11,6 +11,7 @@ export const CatalogView: FC<{}> = () =>
// Default = upstream rebuilt catalog (CatalogClassicView, latest release theme).
// The "stile classico" toggle (or global catalog.classic.style flag) switches
// to the Hippiehotel.nl catalog (CatalogModernView, self-contained tailwind).
// Both the normal catalog and the Builders Club follow this toggle.
if(catalogClassicStyle) return (
<>
<div className="hidden" data-catalog-localization-version={ catalogLocalizationVersion } />
@@ -19,6 +19,13 @@ const TAB_PETS: string = 'inventory.furni.tab.pets';
const TAB_BADGES: string = 'inventory.badges';
const TAB_PREFIXES: string = 'inventory.prefixes';
const TABS = [ TAB_FURNITURE, TAB_PETS, TAB_BADGES, TAB_PREFIXES, TAB_BOTS ];
// Maps an optional link code (inventory/show/<code>) to a tab so other views
// (e.g. the profile "Change Badges" button) can deep-link to a specific tab.
const TAB_BY_CODE: Record<string, string> = {
furni: TAB_FURNITURE, furniture: TAB_FURNITURE,
pets: TAB_PETS, badges: TAB_BADGES,
prefixes: TAB_PREFIXES, bots: TAB_BOTS
};
const UNSEEN_CATEGORIES = [ UnseenItemCategory.FURNI, UnseenItemCategory.PET, UnseenItemCategory.BADGE, UnseenItemCategory.PREFIX, UnseenItemCategory.BOT ];
const TAB_ICONS: Record<string, ReactNode> = {
[TAB_FURNITURE]: <FaCouch />,
@@ -94,12 +101,14 @@ export const InventoryView: FC<{}> = props =>
{
case 'show':
setIsVisible(true);
if(parts[2] && TAB_BY_CODE[parts[2]]) setCurrentTab(TAB_BY_CODE[parts[2]]);
return;
case 'hide':
setIsVisible(false);
return;
case 'toggle':
setIsVisible(prevValue => !prevValue);
if(parts[2] && TAB_BY_CODE[parts[2]]) setCurrentTab(TAB_BY_CODE[parts[2]]);
return;
}
},
+2 -2
View File
@@ -22,7 +22,7 @@ export const NavigatorView: FC<{}> = props =>
{
const { topLevelContext, topLevelContexts, navigatorData, navigatorSearches } = useNavigatorData();
const { searchResult, isFetching } = useNavigatorSearch();
const { isVisible, isCreatorOpen, isRoomInfoOpen, isRoomLinkOpen, isOpenSavesSearches, needsInit } = useNavigatorUiState();
const { isVisible, isCreatorOpen, isRoomInfoOpen, isRoomLinkOpen, isOpenSavesSearches, needsInit, currentTabCode } = useNavigatorUiState();
const elementRef = useRef<HTMLDivElement>(null);
useNitroEvent<RoomSessionEvent>(RoomSessionEvent.CREATED, event =>
@@ -122,7 +122,7 @@ export const NavigatorView: FC<{}> = props =>
{ topLevelContexts && topLevelContexts.length > 0 && topLevelContexts.map((context, index) =>
<NitroCard.TabItem
key={ index }
isActive={ topLevelContext === context && !isCreatorOpen }
isActive={ (currentTabCode ? currentTabCode === context.code : topLevelContext === context) && !isCreatorOpen }
onClick={ () => useNavigatorUiStore.getState().setTab(context.code) }>
{ LocalizeText('navigator.toplevelview.' + context.code) }
</NitroCard.TabItem>) }
@@ -1,6 +1,6 @@
import { RelationshipStatusEnum, RelationshipStatusInfoMessageParser } from '@nitrots/nitro-renderer';
import { FC } from 'react';
import { GetUserProfile, LocalizeText } from '../../api';
import { CreateLinkEvent, GetUserProfile, LocalizeText } from '../../api';
import { Flex, LayoutAvatarImageView } from '../../common';
interface RelationshipsContainerViewProps
@@ -29,7 +29,7 @@ export const RelationshipsContainerView: FC<RelationshipsContainerViewProps> = p
</Flex>
<div className="nitro-extended-profile__relationship-copy">
<div className="nitro-extended-profile__relationship-box">
<p className="nitro-extended-profile__relationship-name" onClick={ event => (relationshipInfo && (relationshipInfo.randomFriendId >= 1) && GetUserProfile(relationshipInfo.randomFriendId)) }>
<p className="nitro-extended-profile__relationship-name" onClick={ event => ((relationshipInfo && (relationshipInfo.randomFriendId >= 1)) ? GetUserProfile(relationshipInfo.randomFriendId) : CreateLinkEvent('friends/toggle')) }>
{ (!relationshipInfo || (relationshipInfo.friendCount === 0)) &&
LocalizeText('extendedprofile.add.friends') }
{ (relationshipInfo && (relationshipInfo.friendCount >= 1)) &&
@@ -37,7 +37,7 @@ export const RelationshipsContainerView: FC<RelationshipsContainerViewProps> = p
</p>
{ (relationshipInfo && (relationshipInfo.friendCount >= 1)) &&
<div className="nitro-extended-profile__relationship-head">
<LayoutAvatarImageView direction={ 4 } figure={ relationshipInfo.randomFriendFigure } headOnly={ true } classNames={ [ '!w-auto', '!h-auto', '!left-0' ] } />
<LayoutAvatarImageView direction={ 2 } figure={ relationshipInfo.randomFriendFigure } headOnly={ true } />
</div> }
</div>
<p className="nitro-extended-profile__relationship-subcopy">
@@ -60,18 +60,12 @@ export const UserContainerView: FC<UserContainerViewProps> = props =>
prefixText={ userProfile.prefixText }
username={ userProfile.username } />
<p className="nitro-extended-profile__motto">{ userProfile.motto || '\u00A0' }</p>
<p
className="nitro-extended-profile__meta"
dangerouslySetInnerHTML={ {
__html: LocalizeText('extendedprofile.created', [ 'created' ], [ userProfile.registration ])
} }
/>
<p
className="nitro-extended-profile__meta"
dangerouslySetInnerHTML={ {
__html: LocalizeText('extendedprofile.last.login', [ 'lastlogin' ], [ FriendlyTime.format(userProfile.secondsSinceLastVisit, '.ago', 2) ])
} }
/>
<p className="nitro-extended-profile__meta">
<b>{ LocalizeText('extendedprofile.created').replace(/%\w+%/g, '').trim() }</b> { userProfile.registration }
</p>
<p className="nitro-extended-profile__meta">
<b>{ LocalizeText('extendedprofile.last.login').replace(/%\w+%/g, '').trim() }</b> { FriendlyTime.format(userProfile.secondsSinceLastVisit, '.ago', 2) }
</p>
<p className="nitro-extended-profile__meta nitro-extended-profile__meta--strong">
<b>{ LocalizeText('extendedprofile.achievementscore') }</b> { userProfile.achievementPoints }
</p>
@@ -100,10 +94,10 @@ export const UserContainerView: FC<UserContainerViewProps> = props =>
{ isOwnProfile &&
<div className="nitro-extended-profile__actions">
<button className="nitro-extended-profile__link" type="button">
<button className="nitro-extended-profile__link" type="button" onClick={ () => CreateLinkEvent('avatar-editor/show') }>
{ LocalizeText('extended.profile.change.looks') }
</button>
<button className="nitro-extended-profile__link" type="button" onClick={ () => CreateLinkEvent('inventory/open/badges') }>
<button className="nitro-extended-profile__link" type="button" onClick={ () => CreateLinkEvent('inventory/show/badges') }>
{ LocalizeText('extended.profile.change.badges') }
</button>
</div> }
@@ -148,11 +142,11 @@ export const UserContainerView: FC<UserContainerViewProps> = props =>
<span className="nitro-extended-profile__summary-label">{ LocalizeText('inventory.badges') }</span>
<span className="nitro-extended-profile__summary-value">{ totalBadges }</span>
</button>
<div className="nitro-extended-profile__summary-button">
<button className="nitro-extended-profile__summary-button nitro-extended-profile__summary-button--center" type="button" onClick={ () => CreateLinkEvent('achievements/toggle') }>
<img className="nitro-extended-profile__summary-icon" src={ profileLevelIcon } alt="" />
<span className="nitro-extended-profile__summary-label">{ LocalizeText('extendedprofile.achievementscore') }</span>
<span className="nitro-extended-profile__summary-value">{ userProfile.achievementPoints }</span>
</div>
</button>
</div>
</div>
);
@@ -1,6 +1,6 @@
import { ExtendedProfileChangedMessageEvent, GetSessionDataManager, NavigatorSearchComposer, RelationshipStatusInfoEvent, RelationshipStatusInfoMessageParser, RoomEngineObjectEvent, RoomObjectCategory, RoomObjectType, UserCurrentBadgesComposer, UserCurrentBadgesEvent, UserProfileEvent, UserProfileParser, UserRelationshipsComposer } from '@nitrots/nitro-renderer';
import { ExtendedProfileChangedMessageEvent, GetSessionDataManager, RelationshipStatusInfoEvent, RelationshipStatusInfoMessageParser, RoomEngineObjectEvent, RoomObjectCategory, RoomObjectType, UserCurrentBadgesComposer, UserCurrentBadgesEvent, UserProfileEvent, UserProfileParser, UserRelationshipsComposer } from '@nitrots/nitro-renderer';
import { FC, useState } from 'react';
import { GetRoomSession, GetUserProfile, LocalizeText, SendMessageComposer } from '../../api';
import { CreateLinkEvent, GetRoomSession, GetUserProfile, LocalizeText, SendMessageComposer } from '../../api';
import { useMessageEvent, useNitroEvent } from '../../hooks';
import { NitroCard } from '../../layout';
import { GroupsContainerView } from './GroupsContainerView';
@@ -28,10 +28,11 @@ export const UserProfileView: FC<{}> = () =>
const onOpenRooms = () =>
{
if(userProfile)
{
SendMessageComposer(new NavigatorSearchComposer('hotel_view', `owner:${ userProfile.username }`));
}
if(!userProfile) return;
// Open the navigator AND run the owner search (the composer alone never
// showed the navigator window, so the button looked dead).
CreateLinkEvent(`navigator/search/hotel_view/owner:${ userProfile.username }`);
};
useMessageEvent<UserCurrentBadgesEvent>(UserCurrentBadgesEvent, event =>
@@ -100,7 +101,7 @@ export const UserProfileView: FC<{}> = () =>
if(!userProfile) return null;
return (
<NitroCard className="nitro-extended-profile-window w-[521px] h-[537px]" uniqueKey="nitro-user-profile">
<NitroCard className="nitro-extended-profile-window w-[600px] h-[600px] max-w-[96vw] max-h-[92vh]" uniqueKey="nitro-user-profile">
<NitroCard.Header
headerText={ LocalizeText('extendedprofile.caption') }
onCloseClick={ onClose } />