This commit is contained in:
Remco
2026-01-27 14:08:40 +01:00
parent 5c88770fa6
commit d987de29b3
344 changed files with 28343 additions and 0 deletions
@@ -0,0 +1,27 @@
import { RelationshipStatusInfoMessageParser } from '@nitrots/nitro-renderer';
import { FC } from 'react';
import { LocalizeText } from '../../api';
import { RelationshipsContainerView } from './RelationshipsContainerView';
interface FriendsContainerViewProps
{
relationships: RelationshipStatusInfoMessageParser;
friendsCount: number;
}
export const FriendsContainerView: FC<FriendsContainerViewProps> = props =>
{
const { relationships = null, friendsCount = null } = props;
return (
<div className="flex flex-col gap-1">
<p className="text-sm">
<b>{ LocalizeText('extendedprofile.friends.count') }</b> { friendsCount }
</p>
<div className="flex flex-col gap-1">
<p className="text-sm font-bold">{ LocalizeText('extendedprofile.relstatus') }</p>
<RelationshipsContainerView relationships={ relationships } />
</div>
</div>
);
};
@@ -0,0 +1,90 @@
import { GroupInformationComposer, GroupInformationEvent, GroupInformationParser, HabboGroupEntryData } from '@nitrots/nitro-renderer';
import { FC, useEffect, useState } from 'react';
import { SendMessageComposer, ToggleFavoriteGroup } from '../../api';
import { AutoGrid, Column, Grid, GridProps, LayoutBadgeImageView, LayoutGridItem } from '../../common';
import { useMessageEvent } from '../../hooks';
import { GroupInformationView } from '../groups/views/GroupInformationView';
interface GroupsContainerViewProps extends GridProps
{
itsMe: boolean;
groups: HabboGroupEntryData[];
onLeaveGroup: () => void;
}
export const GroupsContainerView: FC<GroupsContainerViewProps> = props =>
{
const { itsMe = null, groups = null, onLeaveGroup = null, overflow = 'hidden', gap = 2, ...rest } = props;
const [ selectedGroupId, setSelectedGroupId ] = useState<number>(null);
const [ groupInformation, setGroupInformation ] = useState<GroupInformationParser>(null);
useMessageEvent<GroupInformationEvent>(GroupInformationEvent, event =>
{
const parser = event.getParser();
if(!selectedGroupId || (selectedGroupId !== parser.id) || parser.flag) return;
setGroupInformation(parser);
});
useEffect(() =>
{
if(!selectedGroupId) return;
SendMessageComposer(new GroupInformationComposer(selectedGroupId, false));
}, [ selectedGroupId ]);
useEffect(() =>
{
setGroupInformation(null);
if(groups.length > 0)
{
setSelectedGroupId(prevValue =>
{
if(prevValue === groups[0].groupId)
{
SendMessageComposer(new GroupInformationComposer(groups[0].groupId, false));
}
return groups[0].groupId;
});
}
}, [ groups ]);
if(!groups || !groups.length)
{
return (
<Column center fullHeight>
<div className="flex justify-center gap-2">
<div className="no-group-spritesheet image-1" />
<div className="no-group-spritesheet image-2" />
<div className="no-group-spritesheet image-3" />
</div>
</Column>
);
}
return (
<Grid gap={ 2 } overflow={ overflow } { ...rest }>
<Column alignItems="center" overflow="auto" size={ 2 }>
<AutoGrid className="w-[50px]" columnCount={ 1 } columnMinHeight={ 50 } overflow={ null }>
{ groups.map((group, index) =>
{
return (
<LayoutGridItem key={ index } className="p-1" itemActive={ (selectedGroupId === group.groupId) } overflow="unset" onClick={ () => setSelectedGroupId(group.groupId) }>
{ itsMe &&
<i className={ 'absolute end-0 top-0 z-20 nitro-icon icon-group-' + (group.favourite ? 'favorite' : 'not-favorite') } onClick={ () => ToggleFavoriteGroup(group) } /> }
<LayoutBadgeImageView badgeCode={ group.badgeCode } isGroup={ true } />
</LayoutGridItem>
);
}) }
</AutoGrid>
</Column>
<Column overflow="hidden" size={ 10 }>
{ groupInformation &&
<GroupInformationView groupInformation={ groupInformation } onClose={ onLeaveGroup } /> }
</Column>
</Grid>
);
};
@@ -0,0 +1,62 @@
import { RelationshipStatusEnum, RelationshipStatusInfoMessageParser } from '@nitrots/nitro-renderer';
import { FC } from 'react';
import { GetUserProfile, LocalizeText } from '../../api';
import { Flex, LayoutAvatarImageView } from '../../common';
interface RelationshipsContainerViewProps
{
relationships: RelationshipStatusInfoMessageParser;
}
interface RelationshipsContainerRelationshipViewProps
{
type: number;
}
export const RelationshipsContainerView: FC<RelationshipsContainerViewProps> = props =>
{
const { relationships = null } = props;
const RelationshipComponent = ({ type }: RelationshipsContainerRelationshipViewProps) =>
{
const relationshipInfo = (relationships && relationships.relationshipStatusMap.hasKey(type)) ? relationships.relationshipStatusMap.getValue(type) : null;
const relationshipName = RelationshipStatusEnum.RELATIONSHIP_NAMES[type].toLocaleLowerCase();
return (
<div className="flex w-full gap-1">
<Flex center className="h-[25px]">
<i className={ `nitro-friends-spritesheet icon-${ relationshipName }` } />
</Flex>
<div className="flex flex-col flex-grow gap-0">
<div className="flex items-center justify-between bg-white rounded 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') }
{ (relationshipInfo && (relationshipInfo.friendCount >= 1)) &&
relationshipInfo.randomFriendName }
</p>
{ (relationshipInfo && (relationshipInfo.friendCount >= 1)) &&
<div className="flex items-center justify-center w-[50px] h-[50px] top-[20px] -right-[8px] relative">
<LayoutAvatarImageView direction={ 4 } figure={ relationshipInfo.randomFriendFigure } headOnly={ true } />
</div> }
</div>
<p className="italics text-sm mt-[2px] ml-[5px] !text-[#939392]">
{ (!relationshipInfo || (relationshipInfo.friendCount === 0)) &&
LocalizeText('extendedprofile.no.friends.in.this.category') }
{ (relationshipInfo && (relationshipInfo.friendCount > 1)) &&
LocalizeText(`extendedprofile.relstatus.others.${ relationshipName }`, [ 'count' ], [ (relationshipInfo.friendCount - 1).toString() ]) }
&nbsp;
</p>
</div>
</div>
);
};
return (
<>
<RelationshipComponent type={ RelationshipStatusEnum.HEART } />
<RelationshipComponent type={ RelationshipStatusEnum.SMILE } />
<RelationshipComponent type={ RelationshipStatusEnum.BOBBA } />
</>
);
};
@@ -0,0 +1,71 @@
import { GetSessionDataManager, RequestFriendComposer, UserProfileParser } from '@nitrots/nitro-renderer';
import { FC, useEffect, useState } from 'react';
import { FriendlyTime, LocalizeText, SendMessageComposer } from '../../api';
import { LayoutAvatarImageView, Text } from '../../common';
export const UserContainerView: FC<{
userProfile: UserProfileParser;
}> = props =>
{
const { userProfile = null } = props;
const [ requestSent, setRequestSent ] = useState(userProfile.requestSent);
const isOwnProfile = (userProfile.id === GetSessionDataManager().userId);
const canSendFriendRequest = !requestSent && (!isOwnProfile && !userProfile.isMyFriend && !userProfile.requestSent);
const addFriend = () =>
{
setRequestSent(true);
SendMessageComposer(new RequestFriendComposer(userProfile.username));
};
useEffect(() =>
{
setRequestSent(userProfile.requestSent);
}, [ userProfile ]);
return (
<div className="flex gap-2">
<div className="flex flex-col justify-center items-center w-[75px] h-[120px]">
<LayoutAvatarImageView direction={ 2 } figure={ userProfile.figure } />
</div>
<div className="flex flex-col gap-2">
<div className="flex flex-col gap-0">
<p className="leading-tight">{ userProfile.username }</p>
<p className="text-sm italic leading-tight">{ userProfile.motto }</p>
</div>
<div className="flex flex-col gap-1">
<p className="text-sm leading-none">
<b>{ LocalizeText('extendedprofile.created') }</b> { userProfile.registration }
</p>
<p className="text-sm leading-none">
<b>{ LocalizeText('extendedprofile.last.login') }</b> { FriendlyTime.format(userProfile.secondsSinceLastVisit, '.ago', 2) }
</p>
<p className="text-sm leading-none">
<b>{ LocalizeText('extendedprofile.achievementscore') }</b> { userProfile.achievementPoints }
</p>
</div>
<div className="flex items-center gap-1">
{ userProfile.isOnline &&
<i className="nitro-icon icon-pf-online" /> }
{ !userProfile.isOnline &&
<i className="nitro-icon icon-pf-offline" /> }
<div className="flex items-center gap-1">
{ canSendFriendRequest &&
<Text pointer small underline onClick={ addFriend }>{ LocalizeText('extendedprofile.addasafriend') }</Text> }
{ !canSendFriendRequest &&
<>
<i className="nitro-icon icon-pf-tick" />
{ isOwnProfile &&
<p>{ LocalizeText('extendedprofile.me') }</p> }
{ userProfile.isMyFriend &&
<p>{ LocalizeText('extendedprofile.friend') }</p> }
{ (requestSent || userProfile.requestSent) &&
<p>{ LocalizeText('extendedprofile.friendrequestsent') }</p> }
</> }
</div>
</div>
</div>
</div>
);
};
@@ -0,0 +1,125 @@
import { CreateLinkEvent, 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 { Flex, Grid, LayoutBadgeImageView, Text } from '../../common';
import { useMessageEvent, useNitroEvent } from '../../hooks';
import { NitroCard } from '../../layout';
import { FriendsContainerView } from './FriendsContainerView';
import { GroupsContainerView } from './GroupsContainerView';
import { UserContainerView } from './UserContainerView';
export const UserProfileView: FC<{}> = props =>
{
const [ userProfile, setUserProfile ] = useState<UserProfileParser>(null);
const [ userBadges, setUserBadges ] = useState<string[]>([]);
const [ userRelationships, setUserRelationships ] = useState<RelationshipStatusInfoMessageParser>(null);
const onClose = () =>
{
setUserProfile(null);
setUserBadges([]);
setUserRelationships(null);
};
const onLeaveGroup = () =>
{
if(!userProfile || (userProfile.id !== GetSessionDataManager().userId)) return;
GetUserProfile(userProfile.id);
};
useMessageEvent<UserCurrentBadgesEvent>(UserCurrentBadgesEvent, event =>
{
const parser = event.getParser();
if(!userProfile || (parser.userId !== userProfile.id)) return;
setUserBadges(parser.badges);
});
useMessageEvent<RelationshipStatusInfoEvent>(RelationshipStatusInfoEvent, event =>
{
const parser = event.getParser();
if(!userProfile || (parser.userId !== userProfile.id)) return;
setUserRelationships(parser);
});
useMessageEvent<UserProfileEvent>(UserProfileEvent, event =>
{
const parser = event.getParser();
let isSameProfile = false;
setUserProfile(prevValue =>
{
if(prevValue && prevValue.id) isSameProfile = (prevValue.id === parser.id);
return parser;
});
if(!isSameProfile)
{
setUserBadges([]);
setUserRelationships(null);
}
SendMessageComposer(new UserCurrentBadgesComposer(parser.id));
SendMessageComposer(new UserRelationshipsComposer(parser.id));
});
useMessageEvent<ExtendedProfileChangedMessageEvent>(ExtendedProfileChangedMessageEvent, event =>
{
const parser = event.getParser();
if(parser.userId != userProfile?.id) return;
GetUserProfile(parser.userId);
});
useNitroEvent<RoomEngineObjectEvent>(RoomEngineObjectEvent.SELECTED, event =>
{
if(!userProfile) return;
if(event.category !== RoomObjectCategory.UNIT) return;
const userData = GetRoomSession().userDataManager.getUserDataByIndex(event.objectId);
if(userData.type !== RoomObjectType.USER) return;
GetUserProfile(userData.webID);
});
if(!userProfile) return null;
return (
<NitroCard className="w-[470px] h-[460px]" uniqueKey="nitro-user-profile">
<NitroCard.Header
headerText={ LocalizeText('extendedprofile.caption') }
onCloseClick={ onClose } />
<NitroCard.Content
className="overflow-hidden">
<Grid fullHeight={ false } gap={ 2 }>
<div className="flex flex-col col-span-7 gap-1 border-r border-r-gray pe-2">
<UserContainerView userProfile={ userProfile } />
<div className="flex items-center justify-center w-full gap-3 p-2 rounded bg-muted">
{ userBadges && (userBadges.length > 0) && userBadges.map((badge, index) => <LayoutBadgeImageView key={ badge } badgeCode={ badge } />) }
</div>
</div>
<div className="flex flex-col col-span-5">
{ userRelationships &&
<FriendsContainerView friendsCount={ userProfile.friendsCount } relationships={ userRelationships } /> }
</div>
</Grid>
<Flex alignItems="center" className="px-2 py-1 border-t border-b border-t-gray border-b-gray">
<Flex alignItems="center" gap={ 1 } onClick={ event => CreateLinkEvent(`navigator/search/hotel_view/owner:${ userProfile.username }`) }>
<i className="nitro-icon icon-rooms" />
<Text bold pointer underline>{ LocalizeText('extendedprofile.rooms') }</Text>
</Flex>
</Flex>
<GroupsContainerView fullWidth groups={ userProfile.groups } itsMe={ userProfile.id === GetSessionDataManager().userId } onLeaveGroup={ onLeaveGroup } />
</NitroCard.Content>
</NitroCard>
);
};