Update profiles

This commit is contained in:
Bill 2022-03-14 06:39:09 -04:00
parent 10bb7c5aa7
commit 9ec5f9f0de
13 changed files with 232 additions and 259 deletions

View File

@ -35,8 +35,8 @@ $navigator-height: 420px;
$chat-input-style-selector-widget-width: 210px;
$chat-input-style-selector-widget-height: 200px;
$user-profile-width: 560px;
$user-profile-height: 500px;
$user-profile-width: 470px;
$user-profile-height: 460px;
$nitro-widget-custom-stack-height-width: 275px;
$nitro-widget-custom-stack-height-height: 220px;

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

View File

@ -1,5 +1,5 @@
.layout-grid-item {
height: var(--nitro-grid-column-min-height, 45px);
height: var(--nitro-grid-column-min-height, unset);
background-position: center;
background-repeat: no-repeat;
background-color: $grid-bg-color;

View File

@ -1,106 +1,67 @@
.user-profile {
width: 560px;
.content-area {
color: black;
}
width: $user-profile-width;
height: $user-profile-height;
.user-container {
border-right: 1px solid gray;
.avatar-image {
left: -10px;
.avatar-container {
width: 75px;
height: 120px;
}
.add-friend {
margin: 5px;
margin-left: 10px;
}
}
.badge-container {
min-height: 50px;
background: rgba(0, 0, 0, 0.1);
border-radius: 5px;
margin: 0px;
margin-bottom: 2px;
}
.rooms-button-container {
border-top: 1px solid gray;
border-bottom: 1px solid gray;
padding: 1px;
.rooms-button {
display: inline-block;
text-align: center;
height: 100%;
text-decoration: underline;
margin-left: 10px;
}
}
.friends-container {
height: 100%;
}
}
.user-relationship {
height: 25px;
.profile-groups {
height: 219px;
.profile-groups-item {
.avatar-image-container {
width: 50px;
height: 50px;
border-radius: $border-radius;
border-color: $grid-border-color !important;
background-color: $grid-bg-color;
border: nth(map-values($border-widths), 2) solid;
&.active {
border-color: $grid-active-border-color !important;
background-color: $grid-active-bg-color !important;
}
.icon {
z-index: 1;
top: 0px;
right: 0px;
}
}
}
.relationships-container {
.relationship-container {
.relationship
{
position: relative;
&.advanced {
background-color: white;
padding: 5px;
border-radius: 5px;
}
.relationship-text {
text-decoration: underline;
}
.avatar-image {
position: absolute;
top: 20px;
right: -8pxpx;
}
}
}
.user-relationship-count {
margin-top: 2px;
margin-left: 5px;
color: #939392 !important;
}
.user-groups-container {
.layout-grid-item {
width: 50px;
height: 80px;
right: 0;
margin-top: -60px;
}
}
.others-text {
margin-left: 20px;
height: 21px;
color: #939392;
}
.no-group-spritesheet {
background: transparent url('../../assets/images/groups/no-group-spritesheet.png') no-repeat;
&.image-1 {
width: 95px;
height: 136px;
background-position: -3px -3px;
}
&.image-2 {
width: 95px;
height: 136px;
background-position: -104px -3px;
}
&.image-3 {
width: 95px;
height: 136px;
background-position: -205px -3px;
}
}
}

View File

@ -1,7 +1,7 @@
import { RelationshipStatusInfoEvent, RelationshipStatusInfoMessageParser, UserCurrentBadgesComposer, UserCurrentBadgesEvent, UserProfileEvent, UserProfileParser, UserRelationshipsComposer } from '@nitrots/nitro-renderer';
import { FC, useCallback, useState } from 'react';
import { GetSessionDataManager, GetUserProfile, LocalizeText, SendMessageComposer } from '../../api';
import { Column, Flex, Grid, NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../common';
import { Column, Flex, Grid, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../common';
import { BatchUpdates, UseMessageEventHook } from '../../hooks';
import { BadgesContainerView } from './views/BadgesContainerView';
import { FriendsContainerView } from './views/FriendsContainerView';
@ -26,10 +26,9 @@ export const UserProfileView: FC<{}> = props =>
const onLeaveGroup = useCallback(() =>
{
if(userProfile && userProfile.id === GetSessionDataManager().userId)
{
if(!userProfile || (userProfile.id !== GetSessionDataManager().userId)) return;
GetUserProfile(userProfile.id);
}
}, [ userProfile ]);
const onUserCurrentBadgesEvent = useCallback((event: UserCurrentBadgesEvent) =>
@ -43,7 +42,7 @@ export const UserProfileView: FC<{}> = props =>
UseMessageEventHook(UserCurrentBadgesEvent, onUserCurrentBadgesEvent);
const OnUserRelationshipsEvent = useCallback((event: RelationshipStatusInfoEvent) =>
const onUserRelationshipsEvent = useCallback((event: RelationshipStatusInfoEvent) =>
{
const parser = event.getParser();
@ -52,27 +51,22 @@ export const UserProfileView: FC<{}> = props =>
setUserRelationships(parser);
}, [ userProfile ]);
UseMessageEventHook(RelationshipStatusInfoEvent, OnUserRelationshipsEvent);
UseMessageEventHook(RelationshipStatusInfoEvent, onUserRelationshipsEvent);
const onUserProfileEvent = useCallback((event: UserProfileEvent) =>
{
const parser = event.getParser();
if(userProfile)
{
BatchUpdates(() =>
{
setUserProfile(null);
setUserProfile(parser);
setUserBadges([]);
setUserRelationships(null);
});
}
setUserProfile(parser);
SendMessageComposer(new UserCurrentBadgesComposer(parser.id));
SendMessageComposer(new UserRelationshipsComposer(parser.id));
}, [ userProfile ]);
}, []);
UseMessageEventHook(UserProfileEvent, onUserProfileEvent);
@ -81,22 +75,26 @@ export const UserProfileView: FC<{}> = props =>
return (
<NitroCardView className="user-profile" theme="primary-slim">
<NitroCardHeaderView headerText={ LocalizeText('extendedprofile.caption') } onCloseClick={ onClose } />
<NitroCardContentView>
<Grid>
<Column size={ 7 } className="user-container">
<NitroCardContentView overflow="hidden">
<Grid fullHeight={ false } gap={ 2 }>
<Column size={ 7 } gap={ 1 } className="user-container pe-2">
<UserContainerView userProfile={ userProfile } />
<BadgesContainerView badges={ userBadges } />
<Grid columnCount={ 5 } fullHeight className="bg-muted rounded px-2 py-1">
<BadgesContainerView fullWidth center badges={ userBadges } />
</Grid>
</Column>
<Column size={ 5 }>
{
userRelationships && <FriendsContainerView relationships={userRelationships} friendsCount={userProfile.friendsCount} />
}
{ userRelationships &&
<FriendsContainerView relationships={ userRelationships } friendsCount={ userProfile.friendsCount } /> }
</Column>
</Grid>
<Flex alignItems="center" className="rooms-button-container">
<i className="icon icon-rooms" /><span className="rooms-button">{LocalizeText('extendedprofile.rooms')}</span>
<Flex alignItems="center" className="rooms-button-container px-2 py-1">
<Flex alignItems="center" gap={ 1 }>
<i className="icon icon-rooms" />
<Text bold underline pointer>{ LocalizeText('extendedprofile.rooms') }</Text>
</Flex>
<GroupsContainerView itsMe={ userProfile.id === GetSessionDataManager().userId } groups={ userProfile.groups } onLeaveGroup={ onLeaveGroup } />
</Flex>
<GroupsContainerView fullWidth itsMe={ userProfile.id === GetSessionDataManager().userId } groups={ userProfile.groups } onLeaveGroup={ onLeaveGroup } />
</NitroCardContentView>
</NitroCardView>
)

View File

@ -1,27 +1,25 @@
import { FC } from 'react';
import { Grid, LayoutBadgeImageView, LayoutGridItem } from '../../../common';
import { Column, FlexProps, LayoutBadgeImageView } from '../../../common';
interface BadgesContainerViewProps
interface BadgesContainerViewProps extends FlexProps
{
badges: string[];
}
export const BadgesContainerView: FC<BadgesContainerViewProps> = props =>
{
const { badges = null } = props;
const { badges = null, gap = 1, justifyContent = 'between', ...rest } = props;
return (
<div className="row">
<Grid>
<>
{ badges && (badges.length > 0) && badges.map((badge, index) =>
{
return (
<LayoutGridItem key={ index }>
<LayoutBadgeImageView badgeCode={ badge }/>
</LayoutGridItem>
)
<Column key={ badge } center>
<LayoutBadgeImageView key={ badge } badgeCode={ badge } />
</Column>
);
}) }
</Grid>
</div>
)
</>
);
}

View File

@ -1,6 +1,7 @@
import { RelationshipStatusInfoMessageParser } from '@nitrots/nitro-renderer';
import { FC } from 'react';
import { LocalizeText } from '../../../api';
import { Column, Text } from '../../../common';
import { RelationshipsContainerView } from './RelationshipsContainerView';
interface FriendsContainerViewProps
@ -14,12 +15,14 @@ export const FriendsContainerView: FC<FriendsContainerViewProps> = props =>
const { relationships = null, friendsCount = null } = props;
return (
<div className="friends-container h-100 d-flex flex-column">
<div className="mb-1" dangerouslySetInnerHTML={{ __html: LocalizeText('extendedprofile.friends.count', ['count'], [friendsCount.toString()]) }} />
<div className="mb-1"><b>{LocalizeText('extendedprofile.relstatus')}</b></div>
<div className="h-100 d-flex flex-column justify-content-between">
<Column gap={ 1 }>
<Text small>
<b>{ LocalizeText('extendedprofile.friends.count') }</b> { friendsCount }
</Text>
<Text bold small>{ LocalizeText('extendedprofile.relstatus') }</Text>
<Column>
<RelationshipsContainerView relationships={relationships} />
</div>
</div>
</Column>
</Column>
)
}

View File

@ -1,12 +1,11 @@
import { GroupFavoriteComposer, GroupInformationComposer, GroupInformationEvent, GroupInformationParser, HabboGroupEntryData } from '@nitrots/nitro-renderer';
import classNames from 'classnames';
import { FC, useCallback, useEffect, useState } from 'react';
import { SendMessageComposer } from '../../../api';
import { LayoutBadgeImageView } from '../../../common';
import { UseMessageEventHook } from '../../../hooks';
import { AutoGrid, Base, Column, Flex, Grid, GridProps, LayoutBadgeImageView, LayoutGridItem } from '../../../common';
import { BatchUpdates, UseMessageEventHook } from '../../../hooks';
import { GroupInformationView } from '../../groups/views/GroupInformationView';
interface GroupsContainerViewProps
interface GroupsContainerViewProps extends GridProps
{
itsMe: boolean;
groups: HabboGroupEntryData[];
@ -15,16 +14,17 @@ interface GroupsContainerViewProps
export const GroupsContainerView: FC<GroupsContainerViewProps> = props =>
{
const { itsMe = null, groups = null, onLeaveGroup = null } = 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);
const favoriteGroup = (groupId: number) => SendMessageComposer(new GroupFavoriteComposer(groupId));
const onGroupInformationEvent = useCallback((event: GroupInformationEvent) =>
{
const parser = event.getParser();
if(!selectedGroupId || selectedGroupId !== parser.id || parser.flag) return;
if(!selectedGroupId || (selectedGroupId !== parser.id) || parser.flag) return;
if(groupInformation) setGroupInformation(null);
@ -35,37 +35,54 @@ export const GroupsContainerView: FC<GroupsContainerViewProps> = props =>
useEffect(() =>
{
if(groups.length > 0 && !selectedGroupId) setSelectedGroupId(groups[0].groupId);
}, [ groups, selectedGroupId ]);
if(!selectedGroupId) return;
SendMessageComposer(new GroupInformationComposer(selectedGroupId, false));
}, [ selectedGroupId ]);
useEffect(() =>
{
if(selectedGroupId) SendMessageComposer(new GroupInformationComposer(selectedGroupId, false));
}, [ selectedGroupId ]);
const favoriteGroup = useCallback((groupId: number) =>
BatchUpdates(() =>
{
SendMessageComposer(new GroupFavoriteComposer(groupId));
}, []);
setGroupInformation(null);
if(!groups) return null;
if(groups.length > 0) setSelectedGroupId(groups[0].groupId);
});
}, [ groups ]);
if(!groups || !groups.length)
{
return (
<Column center fullHeight>
<Flex justifyContent="center" gap={ 2 }>
<Base className="no-group-spritesheet image-1" />
<Base className="no-group-spritesheet image-2" />
<Base className="no-group-spritesheet image-3" />
</Flex>
</Column>
);
}
return (
<div className="d-flex">
<div className="profile-groups p-2">
<div className="h-100 overflow-auto d-flex flex-column gap-1">
<Grid overflow={ overflow } gap={ 2 } { ...rest }>
<Column alignItems="center" size={ 2 } overflow="auto">
<AutoGrid overflow={ null } columnCount={ 1 } columnMinHeight={ 50 } className="user-groups-container">
{ groups.map((group, index) =>
{
return <div key={ index } onClick={ () => setSelectedGroupId(group.groupId) } className={ 'profile-groups-item position-relative flex-shrink-0 d-flex align-items-center justify-content-center cursor-pointer' + classNames({ ' active': selectedGroupId === group.groupId }) }>
{ itsMe && <i className={ 'position-absolute icon icon-group-' + (group.favourite ? 'favorite' : 'not-favorite') } onClick={ () => favoriteGroup(group.groupId) } /> }
return (
<LayoutGridItem key={ index } overflow="unset" itemActive={ (selectedGroupId === group.groupId) } onClick={ () => setSelectedGroupId(group.groupId) } className="p-1">
{ itsMe &&
<i className={ 'position-absolute end-0 top-0 z-index-1 icon icon-group-' + (group.favourite ? 'favorite' : 'not-favorite') } onClick={ () => favoriteGroup(group.groupId) } /> }
<LayoutBadgeImageView badgeCode={ group.badgeCode } isGroup={ true } />
</div>
</LayoutGridItem>
)
}) }
</div>
</div>
<div className="w-100">
{ groupInformation && <GroupInformationView groupInformation={ groupInformation } onClose={ onLeaveGroup } /> }
</div>
</div>
</AutoGrid>
</Column>
<Column size={ 10 }>
{ groupInformation &&
<GroupInformationView groupInformation={ groupInformation } onClose={ onLeaveGroup } /> }
</Column>
</Grid>
);
}

View File

@ -1,75 +1,62 @@
import { RelationshipStatusEnum, RelationshipStatusInfo, RelationshipStatusInfoMessageParser } from '@nitrots/nitro-renderer';
import { FC, useCallback } from 'react';
import { RelationshipStatusEnum, RelationshipStatusInfoMessageParser } from '@nitrots/nitro-renderer';
import { FC } from 'react';
import { GetUserProfile, LocalizeText } from '../../../api';
import { LayoutAvatarImageView } from '../../../common';
import { Column, Flex, LayoutAvatarImageView, Text } from '../../../common';
interface RelationshipsContainerViewProps
{
relationships: RelationshipStatusInfoMessageParser;
simple?: boolean;
}
interface RelationshipsContainerRelationshipViewProps
{
type: number;
}
export const RelationshipsContainerView: FC<RelationshipsContainerViewProps> = props =>
{
const { relationships = null, simple = false } = props;
const { relationships = null } = props;
const OnUserClick = useCallback((user: RelationshipStatusInfo) =>
{
if(!user) return;
GetUserProfile(user.randomFriendId);
}, []);
const RelationshipComponent = useCallback(({ type }) =>
const RelationshipComponent = ({ type }: RelationshipsContainerRelationshipViewProps) =>
{
const relationshipInfo = (relationships && relationships.relationshipStatusMap.hasKey(type)) ? relationships.relationshipStatusMap.getValue(type) : null;
if(simple && !relationshipInfo) return null;
const relationshipName = RelationshipStatusEnum.RELATIONSHIP_NAMES[type].toLocaleLowerCase();
return (
<div className="relationship-container d-flex flex-row align-items-center w-100">
<i className={`nitro-friends-spritesheet icon-${relationshipName} flex-shrink-0 align-self-baseline mt-2`} />
<div className="w-100 d-flex flex-column">
<span className={'relationship mx-2' + (!simple ? ' advanced' : '')}>
<span className="cursor-pointer relationship-text" onClick={ event => OnUserClick(relationshipInfo)}>
{
(relationshipInfo && relationshipInfo.friendCount > 0) ? relationshipInfo.randomFriendName : LocalizeText('extendedprofile.add.friends')
}
</span>
{
(simple && relationshipInfo && relationshipInfo.friendCount > 1) &&
<span>
{' ' + LocalizeText(`extendedprofile.relstatus.others.${relationshipName}`, ['count'], [(relationshipInfo.friendCount - 1).toString()])}
</span>
}
{
(!simple && relationshipInfo && relationshipInfo.friendCount > 0) &&
<LayoutAvatarImageView figure={relationshipInfo.randomFriendFigure} headOnly={true} direction={4} />
}
</span>
{
!simple && <div className="others-text">
{
(relationshipInfo && relationshipInfo.friendCount > 1) ? LocalizeText(`extendedprofile.relstatus.others.${relationshipName}`, ['count'], [(relationshipInfo.friendCount - 1).toString()]) : ''
}
{
(relationshipInfo && relationshipInfo.friendCount < 1) ? LocalizeText('extendedprofile.no.friends.in.this.category') : ''
}
</div>
}
</div>
</div>
<Flex fullWidth gap={ 1 }>
<Flex center className="user-relationship">
<i className={ `nitro-friends-spritesheet icon-${ relationshipName }` } />
</Flex>
<Column grow gap={ 0 }>
<Flex alignItems="center" justifyContent="between" className="bg-white rounded px-2 py-1 user-relationship">
<Text small 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 }
</Text>
{ (relationshipInfo && (relationshipInfo.friendCount >= 1)) &&
<Flex center position="relative" className="avatar-image-container">
<LayoutAvatarImageView figure={ relationshipInfo.randomFriendFigure } headOnly={ true } direction={ 4 } />
</Flex> }
</Flex>
<Text small italics className="user-relationship-count">
{ (!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;
</Text>
</Column>
</Flex>
);
}, [OnUserClick, relationships, simple]);
}
return (
<div className="row justify-content-center relationships-container align-items-center flex-fill">
<RelationshipComponent type={RelationshipStatusEnum.HEART} />
<RelationshipComponent type={RelationshipStatusEnum.SMILE} />
<RelationshipComponent type={RelationshipStatusEnum.BOBBA} />
</div>
<>
<RelationshipComponent type={ RelationshipStatusEnum.HEART } />
<RelationshipComponent type={ RelationshipStatusEnum.SMILE } />
<RelationshipComponent type={ RelationshipStatusEnum.BOBBA } />
</>
);
}

View File

@ -1,7 +1,7 @@
import { FriendlyTime, UserProfileParser } from '@nitrots/nitro-renderer';
import { FC, useCallback } from 'react';
import { FC } from 'react';
import { GetSessionDataManager, LocalizeText } from '../../../api';
import { LayoutAvatarImageView } from '../../../common';
import { Button, Column, Flex, LayoutAvatarImageView, Text } from '../../../common';
interface UserContainerViewProps
{
@ -11,42 +11,51 @@ interface UserContainerViewProps
export const UserContainerView: FC<UserContainerViewProps> = props =>
{
const { userProfile = null } = props;
const OnlineIcon = useCallback(() =>
{
if(userProfile.isOnline) return (<i className="icon icon-pf-online" />);
else return (<i className="icon icon-pf-offline" />);
}, [ userProfile ]);
const FriendRequestComponent = useCallback(() =>
{
if(userProfile.id === GetSessionDataManager().userId) return (<span><i className="icon icon-pf-tick" />{LocalizeText('extendedprofile.me')}</span> );
if(userProfile.isMyFriend) return (<span><i className="icon icon-pf-tick" />{LocalizeText('extendedprofile.friend')}</span>);
if(userProfile.requestSent) return (<span><i className="icon icon-pf-tick" />{LocalizeText('extendedprofile.friendrequestsent')}</span>);
return (<button className="btn btn-success btn-sm add-friend">{LocalizeText('extendedprofile.addasafriend')}</button>)
}, [ userProfile ]);
const isOwnProfile = (userProfile.id === GetSessionDataManager().userId);
const canSendFriendRequest = (!isOwnProfile && !userProfile.isMyFriend && !userProfile.requestSent);
return (
<div className="row">
<div className="col-auto px-0 d-flex align-items-center">
<LayoutAvatarImageView figure={userProfile.figure} direction={2} />
</div>
<div className="col">
<div className="user-info-container">
<div className="fw-bold">{userProfile.username}</div>
<div className="fst-italic text-break small">{userProfile.motto}</div>
<div dangerouslySetInnerHTML={{ __html: LocalizeText('extendedprofile.created', ['created'], [userProfile.registration]) }} />
<div dangerouslySetInnerHTML={{ __html: LocalizeText('extendedprofile.last.login', ['lastlogin'], [FriendlyTime.format(userProfile.secondsSinceLastVisit, '.ago', 2)]) }} />
<div><b>{LocalizeText('extendedprofile.achievementscore')}</b> {userProfile.achievementPoints}</div>
<div className="d-flex flex-row align-items-center gap-2">
<OnlineIcon />
<FriendRequestComponent />
</div>
</div>
</div>
</div>
<Flex gap={ 2 }>
<Column center className="avatar-container">
<LayoutAvatarImageView figure={ userProfile.figure } direction={ 2 } />
</Column>
<Column>
<Column gap={ 0}>
<Text bold>{ userProfile.username }</Text>
<Text italics textBreak small>{ userProfile.motto }&nbsp;</Text>
</Column>
<Column gap={ 1 }>
<Text small>
<b>{ LocalizeText('extendedprofile.created') }</b> { userProfile.registration }
</Text>
<Text small>
<b>{ LocalizeText('extendedprofile.last.login') }</b> { FriendlyTime.format(userProfile.secondsSinceLastVisit, '.ago', 2) }
</Text>
<Text small>
<b>{ LocalizeText('extendedprofile.achievementscore') }</b> { userProfile.achievementPoints }
</Text>
</Column>
<Flex gap={ 1 }>
{ userProfile.isOnline &&
<i className="icon icon-pf-online" /> }
{ !userProfile.isOnline &&
<i className="icon icon-pf-offline" /> }
<Flex alignItems="center" gap={ 1 }>
{ canSendFriendRequest &&
<Button variant="success" className="add-friend">{ LocalizeText('extendedprofile.addasafriend') }</Button> }
{ !canSendFriendRequest &&
<>
<i className="icon icon-pf-tick" />
{ isOwnProfile &&
<Text>{ LocalizeText('extendedprofile.me') }</Text> }
{ userProfile.isMyFriend &&
<Text>{ LocalizeText('extendedprofile.friend') }</Text> }
{ userProfile.requestSent &&
<Text>{ LocalizeText('extendedprofile.friendrequestsent') }</Text> }
</> }
</Flex>
</Flex>
</Column>
</Flex>
)
}