diff --git a/src/assets/images/profile/icons/bobba.png b/src/assets/images/profile/icons/bobba.png new file mode 100644 index 00000000..eaea8957 Binary files /dev/null and b/src/assets/images/profile/icons/bobba.png differ diff --git a/src/assets/images/profile/icons/heart.png b/src/assets/images/profile/icons/heart.png new file mode 100644 index 00000000..5dd1015e Binary files /dev/null and b/src/assets/images/profile/icons/heart.png differ diff --git a/src/assets/images/profile/icons/offline.png b/src/assets/images/profile/icons/offline.png new file mode 100644 index 00000000..677aadcf Binary files /dev/null and b/src/assets/images/profile/icons/offline.png differ diff --git a/src/assets/images/profile/icons/online.gif b/src/assets/images/profile/icons/online.gif new file mode 100644 index 00000000..3a79838b Binary files /dev/null and b/src/assets/images/profile/icons/online.gif differ diff --git a/src/assets/images/profile/icons/smile.png b/src/assets/images/profile/icons/smile.png new file mode 100644 index 00000000..62fcae6e Binary files /dev/null and b/src/assets/images/profile/icons/smile.png differ diff --git a/src/assets/images/profile/icons/tick.png b/src/assets/images/profile/icons/tick.png new file mode 100644 index 00000000..ec8c52fd Binary files /dev/null and b/src/assets/images/profile/icons/tick.png differ diff --git a/src/assets/styles/icons.scss b/src/assets/styles/icons.scss index 53cfa487..7543bd5d 100644 --- a/src/assets/styles/icons.scss +++ b/src/assets/styles/icons.scss @@ -513,6 +513,42 @@ height: 21px; } + &.icon-pf-online { + background: url('../images/profile/icons/online.gif'); + width: 40px; + height: 16px; + } + + &.icon-pf-offline { + background: url('../images/profile/icons/offline.png'); + width: 40px; + height: 16px; + } + + &.icon-pf-tick { + background: url('../images/profile/icons/tick.png'); + width: 11px; + height: 10px; + } + + &.icon-relationship-heart { + background: url('../images/profile/icons/heart.png'); + width: 16px; + height: 14px; + } + + &.icon-relationship-bobba { + background: url('../images/profile/icons/bobba.png'); + width: 16px; + height: 14px; + } + + &.icon-relationship-smile { + background: url('../images/profile/icons/smile.png'); + width: 16px; + height: 14px; + } + &.spin { animation: rotating 1s linear infinite; } diff --git a/src/views/Styles.scss b/src/views/Styles.scss index 5a81e730..f79dbf3d 100644 --- a/src/views/Styles.scss +++ b/src/views/Styles.scss @@ -17,3 +17,4 @@ @import './mod-tools/ModToolsView'; @import './achievements/AchievementsView'; @import './user-settings/UserSettingsView'; +@import './user-profile/UserProfileVew'; diff --git a/src/views/friend-list/views/friend-bar-item/FriendBarItemView.tsx b/src/views/friend-list/views/friend-bar-item/FriendBarItemView.tsx index 312cb6b0..765542c4 100644 --- a/src/views/friend-list/views/friend-bar-item/FriendBarItemView.tsx +++ b/src/views/friend-list/views/friend-bar-item/FriendBarItemView.tsx @@ -1,4 +1,4 @@ -import { FollowFriendComposer, MouseEventType } from '@nitrots/nitro-renderer'; +import { FollowFriendComposer, MouseEventType, UserProfileComposer } from '@nitrots/nitro-renderer'; import { FC, useCallback, useEffect, useRef, useState } from 'react'; import { SendMessageHook } from '../../../../hooks/messages'; import { LocalizeText } from '../../../../utils/LocalizeText'; @@ -16,6 +16,11 @@ export const FriendBarItemView: FC = props => SendMessageHook(new FollowFriendComposer(friend.id)); }, [ friend ]); + const openProfile = useCallback(() => + { + SendMessageHook(new UserProfileComposer(friend.id)); + }, [ friend ]); + const onClick = useCallback((event: MouseEvent) => { const element = elementRef.current; @@ -56,7 +61,7 @@ export const FriendBarItemView: FC = props =>
{ friend.followingAllowed && } - +
} ); diff --git a/src/views/main/MainView.tsx b/src/views/main/MainView.tsx index 4b52836f..bcf070d5 100644 --- a/src/views/main/MainView.tsx +++ b/src/views/main/MainView.tsx @@ -14,6 +14,7 @@ import { NotificationCenterView } from '../notification-center/NotificationCente import { RightSideView } from '../right-side/RightSideView'; import { RoomHostView } from '../room-host/RoomHostView'; import { ToolbarView } from '../toolbar/ToolbarView'; +import { UserProfileView } from '../user-profile/UserProfileView'; import { UserSettingsView } from '../user-settings/UserSettingsView'; import { WiredView } from '../wired/WiredView'; import { MainViewProps } from './MainView.types'; @@ -62,6 +63,7 @@ export const MainView: FC = props => + ); } diff --git a/src/views/room/widgets/infostand/views/user/InfoStandWidgetUserView.tsx b/src/views/room/widgets/infostand/views/user/InfoStandWidgetUserView.tsx index 5d8b3cb5..ea65abe3 100644 --- a/src/views/room/widgets/infostand/views/user/InfoStandWidgetUserView.tsx +++ b/src/views/room/widgets/infostand/views/user/InfoStandWidgetUserView.tsx @@ -1,9 +1,11 @@ -import { RoomSessionUserBadgesEvent } from '@nitrots/nitro-renderer'; +import { RelationshipStatusInfoEvent, RelationshipStatusInfoMessageParser, RoomSessionUserBadgesEvent, UserRelationshipsComposer } from '@nitrots/nitro-renderer'; import { FC, FocusEvent, KeyboardEvent, useCallback, useEffect, useState } from 'react'; +import { CreateMessageHook, SendMessageHook } from '../../../../../../hooks'; import { CreateEventDispatcherHook } from '../../../../../../hooks/events'; import { LocalizeText } from '../../../../../../utils/LocalizeText'; import { AvatarImageView } from '../../../../../shared/avatar-image/AvatarImageView'; import { BadgeImageView } from '../../../../../shared/badge-image/BadgeImageView'; +import { RelationshipsContainerView } from '../../../../../user-profile/views/relationships-container/RelationshipsContainerView'; import { useRoomContext } from '../../../../context/RoomContext'; import { RoomWidgetUpdateInfostandUserEvent } from '../../../../events/RoomWidgetUpdateInfostandUserEvent'; import { RoomWidgetChangeMottoMessage } from '../../../../messages'; @@ -16,6 +18,7 @@ export const InfoStandWidgetUserView: FC = props = const [ badges, setBadges ] = useState([]); const [ motto, setMotto ] = useState(null); const [ isEditingMotto, setIsEditingMotto ] = useState(false); + const [ userRelationships, setUserRelationships ] = useState(null); const saveMotto = useCallback((motto: string) => { @@ -50,11 +53,27 @@ export const InfoStandWidgetUserView: FC = props = CreateEventDispatcherHook(RoomSessionUserBadgesEvent.RSUBE_BADGES, eventDispatcher, onRoomSessionUserBadgesEvent); + const OnUserRelationshipsEvent = useCallback((event: RelationshipStatusInfoEvent) => + { + const parser = event.getParser(); + + if (userData && userData.webID === parser.userId) + setUserRelationships(parser); + }, [userData]); + + CreateMessageHook(RelationshipStatusInfoEvent, OnUserRelationshipsEvent); + useEffect(() => { setBadges(userData.badges); setIsEditingMotto(false); setMotto(userData.motto); + SendMessageHook(new UserRelationshipsComposer(userData.webID)); + + return () => { + setBadges([]); + setUserRelationships(null); + } }, [ userData ]); if(!userData) return null; @@ -122,7 +141,9 @@ export const InfoStandWidgetUserView: FC = props =
{ LocalizeText('infostand.text.handitem', [ 'item' ], [ LocalizeText('handitem' + userData.carryItem) ]) }
- } + + } + ); } diff --git a/src/views/toolbar/me/ToolbarMeView.tsx b/src/views/toolbar/me/ToolbarMeView.tsx index 37228c04..1e9a42ef 100644 --- a/src/views/toolbar/me/ToolbarMeView.tsx +++ b/src/views/toolbar/me/ToolbarMeView.tsx @@ -1,5 +1,7 @@ -import { MouseEventType } from '@nitrots/nitro-renderer'; -import { FC, useEffect, useRef } from 'react'; +import { MouseEventType, UserProfileComposer } from '@nitrots/nitro-renderer'; +import { FC, useCallback, useEffect, useRef } from 'react'; +import { GetSessionDataManager } from '../../../api'; +import { SendMessageHook } from '../../../hooks'; import { ToolbarViewItems } from '../ToolbarView.types'; import { ToolbarMeViewProps } from './ToolbarMeView.types'; @@ -29,6 +31,11 @@ export const ToolbarMeView: FC = props => } }, [ elementRef, setMeExpanded ]); + const openProfile = useCallback(() => + { + SendMessageHook(new UserProfileComposer(GetSessionDataManager().userId)); + }, []); + return (
@@ -42,7 +49,7 @@ export const ToolbarMeView: FC = props =>
- + openProfile()}>
diff --git a/src/views/user-profile/UserProfileVew.scss b/src/views/user-profile/UserProfileVew.scss new file mode 100644 index 00000000..d77fabf6 --- /dev/null +++ b/src/views/user-profile/UserProfileVew.scss @@ -0,0 +1,50 @@ +.user-profile { + .content-area { + color: black; + } + + .user-container + { + border-right: 1px solid gray; + + .avatar-image { + left: -10px; + } + + .add-friend { + margin: 5px; + margin-left: 10px; + } + } + + .badge-container + { + min-height: 50px; + background: rgba(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%; + } +} + +@import './views/relationships-container/RelationshipsContainerView'; diff --git a/src/views/user-profile/UserProfileView.tsx b/src/views/user-profile/UserProfileView.tsx new file mode 100644 index 00000000..2865137b --- /dev/null +++ b/src/views/user-profile/UserProfileView.tsx @@ -0,0 +1,95 @@ +import { RelationshipStatusInfoEvent, RelationshipStatusInfoMessageParser, UserCurrentBadgesComposer, UserCurrentBadgesEvent, UserProfileEvent, UserProfileParser, UserRelationshipsComposer } from '@nitrots/nitro-renderer'; +import { FC, useCallback, useState } from 'react'; +import { CreateMessageHook, SendMessageHook } from '../../hooks'; +import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../layout'; +import { LocalizeText } from '../../utils'; +import { BadgesContainerView } from './views/badges-container/BadgesContainerView'; +import { FriendsContainerView } from './views/friends-container/FriendsContainerView'; +import { UserContainerView } from './views/user-container/UserContainerView'; + +export const UserProfileView: FC = props => +{ + const [userProfile, setUserProfile] = useState(null); + const [userBadges, setUserBadges] = useState([]); + const [userRelationships, setUserRelationships] = useState(null); + + const OnClose = useCallback(() => + { + setUserProfile(null); + setUserBadges([]); + setUserRelationships(null); + }, []); + + const OnUserCurrentBadgesEvent = useCallback((event: UserCurrentBadgesEvent) => + { + const parser = event.getParser(); + + if (userProfile && parser.userId === userProfile.id) + setUserBadges(parser.badges); + }, [userProfile, setUserBadges]); + + CreateMessageHook(UserCurrentBadgesEvent, OnUserCurrentBadgesEvent); + + const OnUserRelationshipsEvent = useCallback((event: RelationshipStatusInfoEvent) => + { + const parser = event.getParser(); + + if (userProfile && parser.userId === userProfile.id) + setUserRelationships(parser); + }, [userProfile, setUserRelationships]); + + CreateMessageHook(RelationshipStatusInfoEvent, OnUserRelationshipsEvent); + + const OnUserProfileEvent = useCallback((event: UserProfileEvent) => + { + const parser = event.getParser(); + + if(userProfile && userProfile.id !== parser.id) + { + setUserBadges([]); + setUserRelationships(null); + } + + setUserProfile(parser); + SendMessageHook(new UserCurrentBadgesComposer(parser.id)); + SendMessageHook(new UserRelationshipsComposer(parser.id)); + }, [userProfile]); + + CreateMessageHook(UserProfileEvent, OnUserProfileEvent); + + if (!userProfile) return null; + + return ( +
+ + + +
+
+ + +
+
+ { + userRelationships && + } +
+
+
+
+ {LocalizeText('extendedprofile.rooms')} +
+
+
+
+ groups list goes here +
+
+ group info goes here +
+
+
+
+
+ ) +} diff --git a/src/views/user-profile/views/badges-container/BadgesContainerView.tsx b/src/views/user-profile/views/badges-container/BadgesContainerView.tsx new file mode 100644 index 00000000..4e7eafb2 --- /dev/null +++ b/src/views/user-profile/views/badges-container/BadgesContainerView.tsx @@ -0,0 +1,26 @@ +import { FC } from 'react'; +import { BadgeImageView } from '../../../shared/badge-image/BadgeImageView'; +import { BadgesContainerViewProps } from './BadgesContainerView.types'; + +export const BadgesContainerView: FC = props => +{ + const {badges = null} = props; + + return ( +
+
+
+ { + badges.map( (badge, index) => { + return ( +
+ +
+ ) + }) + } +
+
+
+ ); +} diff --git a/src/views/user-profile/views/badges-container/BadgesContainerView.types.ts b/src/views/user-profile/views/badges-container/BadgesContainerView.types.ts new file mode 100644 index 00000000..d57276ab --- /dev/null +++ b/src/views/user-profile/views/badges-container/BadgesContainerView.types.ts @@ -0,0 +1,3 @@ +export interface BadgesContainerViewProps { + badges: string[]; +} diff --git a/src/views/user-profile/views/friends-container/FriendsContainerView.tsx b/src/views/user-profile/views/friends-container/FriendsContainerView.tsx new file mode 100644 index 00000000..3bb06e9f --- /dev/null +++ b/src/views/user-profile/views/friends-container/FriendsContainerView.tsx @@ -0,0 +1,17 @@ +import { FC } from 'react'; +import { LocalizeText } from '../../../../utils'; +import { RelationshipsContainerView } from '../relationships-container/RelationshipsContainerView'; +import { FriendsContainerViewProps } from './FriendsContainerView.types'; + +export const FriendsContainerView: FC = props => +{ + const { relationships = null, friendsCount = null } = props; + + return ( +
+
+
{LocalizeText('extendedprofile.relstatus')}
+ +
+ ) +} diff --git a/src/views/user-profile/views/friends-container/FriendsContainerView.types.ts b/src/views/user-profile/views/friends-container/FriendsContainerView.types.ts new file mode 100644 index 00000000..99ba31df --- /dev/null +++ b/src/views/user-profile/views/friends-container/FriendsContainerView.types.ts @@ -0,0 +1,6 @@ +import { RelationshipStatusInfoMessageParser } from '@nitrots/nitro-renderer'; + +export interface FriendsContainerViewProps { + relationships: RelationshipStatusInfoMessageParser; + friendsCount: number; +} diff --git a/src/views/user-profile/views/relationships-container/RelationshipsContainerView.scss b/src/views/user-profile/views/relationships-container/RelationshipsContainerView.scss new file mode 100644 index 00000000..cc722c8f --- /dev/null +++ b/src/views/user-profile/views/relationships-container/RelationshipsContainerView.scss @@ -0,0 +1,38 @@ +.relationships-container { + + .relationship-container { + //margin-bottom: 10px; + + .relationship + { + margin-left: 10px; + display: inline-block; + + &.advanced { + background-color: white; + padding: 5px; + border-radius: 5px; + } + + .relationship-text { + text-decoration: underline; + } + + .avatar-image { + position: absolute; + width: 50px; + height: 80px; + right: 0; + margin-top: -60px; + margin-right: 10px; + } + } + + .others-text { + margin-left: 20px; + height: 21px; + color: #939392; + } + } + +} diff --git a/src/views/user-profile/views/relationships-container/RelationshipsContainerView.tsx b/src/views/user-profile/views/relationships-container/RelationshipsContainerView.tsx new file mode 100644 index 00000000..be21d944 --- /dev/null +++ b/src/views/user-profile/views/relationships-container/RelationshipsContainerView.tsx @@ -0,0 +1,69 @@ +import { RelationshipStatusEnum, RelationshipStatusInfo, UserProfileComposer } from '@nitrots/nitro-renderer'; +import { FC, useCallback } from 'react'; +import { SendMessageHook } from '../../../../hooks'; +import { LocalizeText } from '../../../../utils'; +import { AvatarImageView } from '../../../shared/avatar-image/AvatarImageView'; +import { RelationshipsContainerViewProps } from './RelationshipsContainerView.types'; + +export const RelationshipsContainerView: FC = props => +{ + const { relationships = null, simple = false } = props; + + const OnUserClick = useCallback((user: RelationshipStatusInfo) => + { + if (user) + SendMessageHook(new UserProfileComposer(user.randomFriendId)); + }, []); + + const RelationshipComponent = useCallback(({ type }) => + { + 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 ( +
+ + + OnUserClick(relationshipInfo)}> + { + (relationshipInfo && relationshipInfo.friendCount > 0) ? relationshipInfo.randomFriendName : LocalizeText('extendedprofile.add.friends') + } + + { + (simple && relationshipInfo && relationshipInfo.friendCount > 1) && + + {' ' + LocalizeText(`extendedprofile.relstatus.others.${relationshipName}`, ['count'], [(relationshipInfo.friendCount - 1).toString()])} + + } + { + (!simple && relationshipInfo && relationshipInfo.friendCount > 0) && + + } + + + { + !simple &&
+ { + (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') : '' + } +
+ } + +
+ ); + }, [OnUserClick, relationships, simple]); + + return ( +
+ + + +
+ ); +} diff --git a/src/views/user-profile/views/relationships-container/RelationshipsContainerView.types.ts b/src/views/user-profile/views/relationships-container/RelationshipsContainerView.types.ts new file mode 100644 index 00000000..2bf0d119 --- /dev/null +++ b/src/views/user-profile/views/relationships-container/RelationshipsContainerView.types.ts @@ -0,0 +1,7 @@ +import { RelationshipStatusInfoMessageParser } from '@nitrots/nitro-renderer'; + +export interface RelationshipsContainerViewProps +{ + relationships: RelationshipStatusInfoMessageParser; + simple?: boolean; +} diff --git a/src/views/user-profile/views/user-container/UserContainerView.tsx b/src/views/user-profile/views/user-container/UserContainerView.tsx new file mode 100644 index 00000000..5cd9393d --- /dev/null +++ b/src/views/user-profile/views/user-container/UserContainerView.tsx @@ -0,0 +1,45 @@ +import { FriendlyTime } from '@nitrots/nitro-renderer'; +import { FC, useCallback } from 'react'; +import { GetSessionDataManager } from '../../../../api'; +import { LocalizeText } from '../../../../utils'; +import { AvatarImageView } from '../../../shared/avatar-image/AvatarImageView'; +import { UserContainerViewProps } from './UserContainerView.types'; + +export const UserContainerView: FC = props => +{ + const {figure = null, username = null, motto = null, creation = null, secondsSinceLastLogin = null, achievementScore, isFriend = null, isOnline = null, id = null, requestSent = null} = props; + + const OnlineIcon = useCallback(() => { + if(isOnline) return (); + else return (); + }, [isOnline]); + + const FriendRequestComponent = useCallback(() => { + if(id === GetSessionDataManager().userId) return ({LocalizeText('extendedprofile.me')} ); + + if(isFriend) return ({LocalizeText('extendedprofile.friend')}); + + if(requestSent) return ({LocalizeText('extendedprofile.friendrequestsent')}); + + return () + }, [id, isFriend, requestSent]); + + return ( +
+
+ +
+
+
+
{username}
+
{motto}
+
+
+
{LocalizeText('extendedprofile.achievementscore')} {achievementScore}
+ + +
+
+
+ ) +} diff --git a/src/views/user-profile/views/user-container/UserContainerView.types.ts b/src/views/user-profile/views/user-container/UserContainerView.types.ts new file mode 100644 index 00000000..9ef01c32 --- /dev/null +++ b/src/views/user-profile/views/user-container/UserContainerView.types.ts @@ -0,0 +1,12 @@ +export interface UserContainerViewProps { + id: number; + username: string; + figure: string; + motto: string; + creation: string; + secondsSinceLastLogin: number; + achievementScore: number; + isFriend: boolean; + requestSent: boolean; + isOnline: boolean; +}