diff --git a/src/api/groups/GetGroupInformation.ts b/src/api/groups/GetGroupInformation.ts new file mode 100644 index 00000000..7f37e004 --- /dev/null +++ b/src/api/groups/GetGroupInformation.ts @@ -0,0 +1,7 @@ +import { GroupInformationComposer } from '@nitrots/nitro-renderer'; +import { SendMessageHook } from '../../hooks'; + +export function GetGroupInformation(groupId: number): void +{ + SendMessageHook(new GroupInformationComposer(groupId, true)); +} diff --git a/src/api/groups/index.ts b/src/api/groups/index.ts new file mode 100644 index 00000000..cc1c46d3 --- /dev/null +++ b/src/api/groups/index.ts @@ -0,0 +1 @@ +export * from './GetGroupInformation'; diff --git a/src/api/index.ts b/src/api/index.ts index c44fa18e..77777005 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -1,4 +1,5 @@ export * from './core'; +export * from './groups'; export * from './navigator'; export * from './nitro'; export * from './utils'; diff --git a/src/assets/styles/fonts.scss b/src/assets/styles/fonts.scss index ea9baf34..2dabd11f 100644 --- a/src/assets/styles/fonts.scss +++ b/src/assets/styles/fonts.scss @@ -3,6 +3,11 @@ src: url('../webfonts/Ubuntu.ttf'); } +@font-face { + font-family: GameUbuntu; + src: url('../webfonts/Ubuntu-C.ttf'); +} + @font-face { font-family: UbuntuItalics; src: url('../webfonts/Ubuntu-i.ttf'); diff --git a/src/assets/webfonts/Ubuntu-C.ttf b/src/assets/webfonts/Ubuntu-C.ttf new file mode 100644 index 00000000..8e2c4bc2 Binary files /dev/null and b/src/assets/webfonts/Ubuntu-C.ttf differ diff --git a/src/index.scss b/src/index.scss index 73ca3e87..cbfa7106 100644 --- a/src/index.scss +++ b/src/index.scss @@ -3,7 +3,7 @@ html, body { - font-family: 'Ubuntu Condensed', sans-serif; + font-family: GameUbuntu, sans-serif; margin: 0; padding: 0; width: 100%; @@ -12,8 +12,6 @@ body { user-select: none; image-rendering: pixelated; image-rendering: -moz-crisp-edges; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; } @import './App'; diff --git a/src/views/groups/GroupView.scss b/src/views/groups/GroupView.scss index c7841e34..44eafba0 100644 --- a/src/views/groups/GroupView.scss +++ b/src/views/groups/GroupView.scss @@ -1 +1,3 @@ @import './views/information/GroupInformationView'; +@import './views/information-standalone/GroupInformationStandaloneView'; +@import './views/room-information/GroupRoomInformationView'; diff --git a/src/views/groups/GroupsView.tsx b/src/views/groups/GroupsView.tsx index 5e42c775..c4e95a69 100644 --- a/src/views/groups/GroupsView.tsx +++ b/src/views/groups/GroupsView.tsx @@ -1,6 +1,11 @@ import { FC } from 'react'; +import { GroupInformationBoxView } from './views/information-standalone/GroupInformationStandaloneView'; export const GroupsView: FC<{}> = props => { - return null; + return ( + <> + + + ); }; diff --git a/src/views/groups/views/information-standalone/GroupInformationStandaloneView.scss b/src/views/groups/views/information-standalone/GroupInformationStandaloneView.scss new file mode 100644 index 00000000..c431fe7c --- /dev/null +++ b/src/views/groups/views/information-standalone/GroupInformationStandaloneView.scss @@ -0,0 +1,3 @@ +.nitro-group-information-standalone { + width: 500px; +} diff --git a/src/views/groups/views/information-standalone/GroupInformationStandaloneView.tsx b/src/views/groups/views/information-standalone/GroupInformationStandaloneView.tsx new file mode 100644 index 00000000..5d43f920 --- /dev/null +++ b/src/views/groups/views/information-standalone/GroupInformationStandaloneView.tsx @@ -0,0 +1,35 @@ +import { GroupInformationEvent, GroupInformationParser } from '@nitrots/nitro-renderer'; +import { FC, useCallback, useState } from 'react'; +import { LocalizeText } from '../../../../api'; +import { CreateMessageHook } from '../../../../hooks'; +import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../layout'; +import { GroupInformationView } from '../information/GroupInformationView'; + +export const GroupInformationBoxView: FC<{}> = props => +{ + const [ groupInformation, setGroupInformation ] = useState(null); + + const onGroupInformationEvent = useCallback((event: GroupInformationEvent) => + { + const parser = event.getParser(); + + if(!parser.flag) return; + + if(groupInformation) setGroupInformation(null); + + setGroupInformation(parser); + }, [ groupInformation ]); + + CreateMessageHook(GroupInformationEvent, onGroupInformationEvent); + + if(!groupInformation) return null; + + return ( + + setGroupInformation(null) } /> + + setGroupInformation(null) } /> + + + ); +}; diff --git a/src/views/groups/views/information/GroupInformationView.tsx b/src/views/groups/views/information/GroupInformationView.tsx index 53607853..8d4837d0 100644 --- a/src/views/groups/views/information/GroupInformationView.tsx +++ b/src/views/groups/views/information/GroupInformationView.tsx @@ -1,7 +1,8 @@ -import { GroupInformationComposer, GroupInformationEvent, GroupInformationParser, GroupJoinComposer, GroupRemoveMemberComposer } from '@nitrots/nitro-renderer'; -import { FC, useCallback, useEffect, useState } from 'react'; -import { GetSessionDataManager, LocalizeText } from '../../../../api'; -import { CreateMessageHook, SendMessageHook } from '../../../../hooks'; +import { GroupDeleteComposer, GroupInformationComposer, GroupJoinComposer, GroupRemoveMemberComposer } from '@nitrots/nitro-renderer'; +import { FC, useCallback } from 'react'; +import { CreateLinkEvent, GetSessionDataManager, LocalizeText, TryVisitRoom } from '../../../../api'; +import { SendMessageHook } from '../../../../hooks'; +import { CatalogPageName } from '../../../catalog/common/CatalogPageName'; import { BadgeImageView } from '../../../shared/badge-image/BadgeImageView'; import { GroupMembershipType } from '../../common/GroupMembershipType'; import { GroupType } from '../../common/GroupType'; @@ -9,26 +10,7 @@ import { GroupInformationViewProps } from './GroupInformationView.types'; export const GroupInformationView: FC = props => { - const { group = null, onLeaveGroup = null } = props; - - const [ groupInformation, setGroupInformation ] = useState(null); - - useEffect(() => - { - setGroupInformation(null); - if(group) SendMessageHook(new GroupInformationComposer(group.id, false)); - }, [ group ]); - - const onGroupInformationEvent = useCallback((event: GroupInformationEvent) => - { - const parser = event.getParser(); - - if(groupInformation) setGroupInformation(null); - - setGroupInformation(parser); - }, [ groupInformation ]); - - CreateMessageHook(GroupInformationEvent, onGroupInformationEvent); + const { groupInformation = null, onClose = null } = props; const tryJoinGroup = useCallback(() => { @@ -42,32 +24,32 @@ export const GroupInformationView: FC = props => { SendMessageHook(new GroupRemoveMemberComposer(groupInformation.id, GetSessionDataManager().userId)); SendMessageHook(new GroupInformationComposer(groupInformation.id, false)); - if(onLeaveGroup) onLeaveGroup(); - }, [ groupInformation, onLeaveGroup ]); + if(onClose) onClose(); + }, [ groupInformation, onClose ]); - const isOwner = useCallback(() => + const isRealOwner = useCallback(() => { - if(!group) return false; + if(!groupInformation) return false; - return (group.ownerId === GetSessionDataManager().userId); - }, [ group, groupInformation ]); + return (groupInformation.ownerName === GetSessionDataManager().userName); + }, [ groupInformation ]); const getRoleIcon = useCallback(() => { if(groupInformation.membershipType === GroupMembershipType.NOT_MEMBER || groupInformation.membershipType === GroupMembershipType.REQUEST_PENDING) return null; - if(isOwner()) return ; + if(isRealOwner()) return ; if(groupInformation.isAdmin) return ; return ; - }, [ groupInformation, isOwner ]); + }, [ groupInformation, isRealOwner ]); const getButtonText = useCallback(() => { if(groupInformation.type === GroupType.PRIVATE) return ''; - if(isOwner()) return 'group.youareowner'; + if(isRealOwner()) return 'group.youareowner'; if(groupInformation.membershipType === GroupMembershipType.MEMBER) return 'group.leave'; @@ -79,7 +61,7 @@ export const GroupInformationView: FC = props => if(groupInformation.membershipType === GroupMembershipType.REQUEST_PENDING) return 'group.membershippending'; } - }, [ groupInformation, isOwner ]); + }, [ groupInformation, isRealOwner ]); const handleButtonClick = useCallback(() => { @@ -89,14 +71,34 @@ export const GroupInformationView: FC = props => return tryJoinGroup(); }, [ groupInformation, tryLeaveGroup, tryJoinGroup ]); + + const handleAction = useCallback((action: string) => + { + switch(action) + { + case 'homeroom': + TryVisitRoom(groupInformation.roomId); + break; + case 'furniture': + CreateLinkEvent('catalog/open/' + CatalogPageName.GUILD_CUSTOM_FURNI); + break; + case 'delete': + if(window.confirm(LocalizeText('group.deleteconfirm.title') + ' - ' + LocalizeText('group.deleteconfirm.desc'))) + { + SendMessageHook(new GroupDeleteComposer(groupInformation.id)); + if(onClose) onClose(); + } + break; + } + }, [ groupInformation, onClose ]); - if(!group || !groupInformation) return null; + if(!groupInformation) return null; return ( -
+
{ groupInformation.canMembersDecorate && } -
{ group.title }
+
{ groupInformation.title }
{ LocalizeText('group.created', ['date', 'owner'], [groupInformation.createdAt, groupInformation.ownerName]) }
{ groupInformation.description }
@@ -136,7 +143,7 @@ export const GroupInformationView: FC = props =>
{ groupInformation.type !== GroupType.PRIVATE && - } diff --git a/src/views/groups/views/information/GroupInformationView.types.ts b/src/views/groups/views/information/GroupInformationView.types.ts index 9543b88f..b8544e6f 100644 --- a/src/views/groups/views/information/GroupInformationView.types.ts +++ b/src/views/groups/views/information/GroupInformationView.types.ts @@ -1,7 +1,7 @@ -import { GroupDataParser } from '@nitrots/nitro-renderer'; +import { GroupInformationParser } from '@nitrots/nitro-renderer'; export interface GroupInformationViewProps { - group: GroupDataParser; - onLeaveGroup?: () => void; + groupInformation: GroupInformationParser; + onClose?: () => void; } diff --git a/src/views/groups/views/room-information/GroupRoomInformationView.scss b/src/views/groups/views/room-information/GroupRoomInformationView.scss new file mode 100644 index 00000000..9c1ac19c --- /dev/null +++ b/src/views/groups/views/room-information/GroupRoomInformationView.scss @@ -0,0 +1,13 @@ +.nitro-group-room-information { + pointer-events: all; + padding: 2px; + background-color: $gable-green; + border: 2px solid rgba($white, 0.5); + font-size: $font-size-sm; + margin-bottom: 5px; + + .group-badge { + width: 50px; + height: 50px; + } +} diff --git a/src/views/groups/views/room-information/GroupRoomInformationView.tsx b/src/views/groups/views/room-information/GroupRoomInformationView.tsx new file mode 100644 index 00000000..1daeb313 --- /dev/null +++ b/src/views/groups/views/room-information/GroupRoomInformationView.tsx @@ -0,0 +1,123 @@ +import { DesktopViewEvent, GroupInformationComposer, GroupInformationEvent, GroupInformationParser, GroupJoinComposer, GroupRemoveMemberComposer, RoomInfoEvent } from '@nitrots/nitro-renderer'; +import { FC, useCallback, useState } from 'react'; +import { GetGroupInformation, GetSessionDataManager, LocalizeText } from '../../../../api'; +import { CreateMessageHook, SendMessageHook } from '../../../../hooks'; +import { BadgeImageView } from '../../../shared/badge-image/BadgeImageView'; +import { GroupMembershipType } from '../../common/GroupMembershipType'; +import { GroupType } from '../../common/GroupType'; + +export const GroupRoomInformationView: FC<{}> = props => +{ + const [ groupId, setGroupId ] = useState(null); + const [ groupInformation, setGroupInformation ] = useState(null); + const [ isExpended, setIsExpended ] = useState(true); + + const onRoomInfoEvent = useCallback((event: RoomInfoEvent) => + { + const parser = event.getParser(); + + setGroupInformation(null); + + if(parser.data.habboGroupId) + { + setGroupId(parser.data.habboGroupId); + SendMessageHook(new GroupInformationComposer(parser.data.habboGroupId, false)); + } + }, []); + + CreateMessageHook(RoomInfoEvent, onRoomInfoEvent); + + const onGroupInformationEvent = useCallback((event: GroupInformationEvent) => + { + const parser = event.getParser(); + + if(parser.flag || groupId !== parser.id) return; + + setGroupInformation(null); + setGroupInformation(parser); + }, [ groupId ]); + + CreateMessageHook(GroupInformationEvent, onGroupInformationEvent); + + const onDesktopViewEvent = useCallback((event: DesktopViewEvent) => + { + setGroupId(0); + setGroupInformation(null); + }, []); + + CreateMessageHook(DesktopViewEvent, onDesktopViewEvent); + + const isRealOwner = useCallback(() => + { + if(!groupInformation) return false; + + return (groupInformation.ownerName === GetSessionDataManager().userName); + }, [ groupInformation ]); + + const tryJoinGroup = useCallback(() => + { + if(!groupInformation) return; + + SendMessageHook(new GroupJoinComposer(groupInformation.id)); + SendMessageHook(new GroupInformationComposer(groupInformation.id, false)); + }, [ groupInformation ]); + + const tryLeaveGroup = useCallback(() => + { + SendMessageHook(new GroupRemoveMemberComposer(groupInformation.id, GetSessionDataManager().userId)); + SendMessageHook(new GroupInformationComposer(groupInformation.id, false)); + }, [ groupInformation ]); + + const getButtonText = useCallback(() => + { + if(groupInformation.type === GroupType.PRIVATE) return ''; + + if(isRealOwner()) return 'group.youareowner'; + + if(groupInformation.membershipType === GroupMembershipType.MEMBER) return 'group.leave'; + + if(groupInformation.membershipType === GroupMembershipType.NOT_MEMBER && groupInformation.type === GroupType.REGULAR) return 'group.join'; + + if(groupInformation.type === GroupType.EXCLUSIVE) + { + if(groupInformation.membershipType === GroupMembershipType.NOT_MEMBER) return 'group.requestmembership'; + + if(groupInformation.membershipType === GroupMembershipType.REQUEST_PENDING) return 'group.membershippending'; + } + }, [ groupInformation, isRealOwner ]); + + const handleButtonClick = useCallback(() => + { + if(groupInformation.type === GroupType.PRIVATE && groupInformation.membershipType === GroupMembershipType.NOT_MEMBER) return; + + if(groupInformation.membershipType === GroupMembershipType.MEMBER) return tryLeaveGroup(); + + return tryJoinGroup(); + }, [ groupInformation, tryLeaveGroup, tryJoinGroup ]); + + if(!groupInformation) return null; + + return ( +
+
setIsExpended(value => !value) }> +
{ LocalizeText('group.homeroominfo.title') }
+ +
+ { isExpended && <> +
GetGroupInformation(groupInformation.id) }> +
+ +
+
+ { groupInformation.title } +
+
+ { groupInformation.type !== GroupType.PRIVATE && !isRealOwner() && + + } + } +
+ ); +}; diff --git a/src/views/main/MainView.tsx b/src/views/main/MainView.tsx index 657f778c..88ba1efe 100644 --- a/src/views/main/MainView.tsx +++ b/src/views/main/MainView.tsx @@ -6,6 +6,7 @@ import { AchievementsView } from '../achievements/AchievementsView'; import { AvatarEditorView } from '../avatar-editor/AvatarEditorView'; import { CatalogView } from '../catalog/CatalogView'; import { FriendListView } from '../friend-list/FriendListView'; +import { GroupsView } from '../groups/GroupsView'; import { HotelView } from '../hotel-view/HotelView'; import { InventoryView } from '../inventory/InventoryView'; import { ModToolsView } from '../mod-tools/ModToolsView'; @@ -62,6 +63,7 @@ export const MainView: FC = props => +
); } diff --git a/src/views/right-side/RightSideView.tsx b/src/views/right-side/RightSideView.tsx index 5917174e..c503a533 100644 --- a/src/views/right-side/RightSideView.tsx +++ b/src/views/right-side/RightSideView.tsx @@ -1,4 +1,5 @@ import { FC } from 'react'; +import { GroupRoomInformationView } from '../groups/views/room-information/GroupRoomInformationView'; import { NotificationCenterView } from '../notification-center/NotificationCenterView'; import { PurseView } from '../purse/PurseView'; import { RightSideProps } from './RightSideView.types'; @@ -9,6 +10,7 @@ export const RightSideView: FC = props =>
+
diff --git a/src/views/room/widgets/chat/message/ChatWidgetMessageView.scss b/src/views/room/widgets/chat/message/ChatWidgetMessageView.scss index e2bbbbbf..9bdbcb6b 100644 --- a/src/views/room/widgets/chat/message/ChatWidgetMessageView.scss +++ b/src/views/room/widgets/chat/message/ChatWidgetMessageView.scss @@ -62,7 +62,7 @@ word-break: break-word; max-width: 350px; min-height: 25px; - font-size: $font-size-sm; + font-size: 15px; border-image-slice: 17 6 6 29 fill; border-image-width: 17px 6px 6px 29px; diff --git a/src/views/user-profile/views/groups-container/GroupsContainerView.tsx b/src/views/user-profile/views/groups-container/GroupsContainerView.tsx index 9e10dafa..dc7ee616 100644 --- a/src/views/user-profile/views/groups-container/GroupsContainerView.tsx +++ b/src/views/user-profile/views/groups-container/GroupsContainerView.tsx @@ -1,5 +1,7 @@ +import { GroupInformationComposer, GroupInformationEvent, GroupInformationParser } from '@nitrots/nitro-renderer'; import classNames from 'classnames'; -import { FC, useEffect, useState } from 'react'; +import { FC, useCallback, useEffect, useState } from 'react'; +import { CreateMessageHook, SendMessageHook } from '../../../../hooks'; import { GroupInformationView } from '../../../groups/views/information/GroupInformationView'; import { BadgeImageView } from '../../../shared/badge-image/BadgeImageView'; import { GroupsContainerViewProps } from './GroupsContainerView.types'; @@ -8,12 +10,31 @@ export const GroupsContainerView: FC = props => { const { groups = null, onLeaveGroup = null } = props; - const [ selectedIndex, setSelectedIndex ] = useState(null); + const [ selectedGroupId, setSelectedGroupId ] = useState(null); + const [ groupInformation, setGroupInformation ] = useState(null); + + const onGroupInformationEvent = useCallback((event: GroupInformationEvent) => + { + const parser = event.getParser(); + + if(!selectedGroupId || selectedGroupId !== parser.id || parser.flag) return; + + if(groupInformation) setGroupInformation(null); + + setGroupInformation(parser); + }, [ groupInformation, selectedGroupId ]); + + CreateMessageHook(GroupInformationEvent, onGroupInformationEvent); useEffect(() => { - if(groups.length > 0 && selectedIndex === null) setSelectedIndex(0); - }, [ groups ]); + if(groups.length > 0 && !selectedGroupId) setSelectedGroupId(groups[0].id); + }, [ groups, selectedGroupId ]); + + useEffect(() => + { + if(selectedGroupId) SendMessageHook(new GroupInformationComposer(selectedGroupId, false)); + }, [ selectedGroupId ]); if(!groups) return null; @@ -23,14 +44,14 @@ export const GroupsContainerView: FC = props =>
{ groups.map((group, index) => { - return
setSelectedIndex(index) } className={ 'profile-groups-item flex-shrink-0 d-flex align-items-center justify-content-center cursor-pointer' + classNames({ ' active': selectedIndex === index }) }> + return
setSelectedGroupId(group.id) } className={ 'profile-groups-item flex-shrink-0 d-flex align-items-center justify-content-center cursor-pointer' + classNames({ ' active': selectedGroupId === group.id }) }>
}) }
- { selectedIndex > -1 && } + { groupInformation && }
); diff --git a/src/views/wired/WiredView.scss b/src/views/wired/WiredView.scss index ce1bc799..eba870c2 100644 --- a/src/views/wired/WiredView.scss +++ b/src/views/wired/WiredView.scss @@ -3,36 +3,52 @@ padding:7px; .icon { - width: 16px; - height: 9px; background-repeat: no-repeat; background-position: center; &.icon-mv-1 { + width: 16px; + height: 9px; background-image: url('../../assets/images/wired/icon_wired_around.png'); } &.icon-mv-2 { + width: 16px; + height: 9px; background-image: url('../../assets/images/wired/icon_wired_up_down.png'); } &.icon-mv-3 { + width: 16px; + height: 9px; background-image: url('../../assets/images/wired/icon_wired_left_right.png'); } &.icon-ne { + width: 16px; + height: 9px; background-image: url('../../assets/images/wired/icon_wired_north_east.png'); } &.icon-se { + width: 16px; + height: 9px; background-image: url('../../assets/images/wired/icon_wired_south_east.png'); } &.icon-sw { + width: 16px; + height: 9px; background-image: url('../../assets/images/wired/icon_wired_south_west.png'); } &.icon-nw { + width: 16px; + height: 9px; background-image: url('../../assets/images/wired/icon_wired_north_west.png'); } &.icon-rot-1 { + width: 16px; + height: 9px; background-image: url('../../assets/images/wired/icon_wired_rotate_clockwise.png'); } &.icon-rot-2 { + width: 16px; + height: 9px; background-image: url('../../assets/images/wired/icon_wired_rotate_counter_clockwise.png'); } }