diff --git a/src/events/navigator/NavigatorEvent.ts b/src/events/navigator/NavigatorEvent.ts index 0ee65a70..19f3a81c 100644 --- a/src/events/navigator/NavigatorEvent.ts +++ b/src/events/navigator/NavigatorEvent.ts @@ -7,6 +7,7 @@ export class NavigatorEvent extends NitroEvent public static TOGGLE_NAVIGATOR: string = 'NE_TOGGLE_NAVIGATOR'; public static TOGGLE_ROOM_INFO: string = 'NE_TOGGLE_ROOM_INFO'; public static TOGGLE_ROOM_LINK: string = 'NE_TOGGLE_ROOM_LINK'; + public static TOGGLE_ROOM_SETTINGS: string = 'NE_TOGGLE_ROOM_SETTINGS'; private _roomId: number; private _password: string; diff --git a/src/events/room-widgets/thumbnail/RoomWidgetThumbnailEvent.ts b/src/events/room-widgets/thumbnail/RoomWidgetThumbnailEvent.ts new file mode 100644 index 00000000..03451226 --- /dev/null +++ b/src/events/room-widgets/thumbnail/RoomWidgetThumbnailEvent.ts @@ -0,0 +1,8 @@ +import { RoomWidgetUpdateEvent } from '../../../views/room/events/RoomWidgetUpdateEvent'; + +export class RoomWidgetThumbnailEvent extends RoomWidgetUpdateEvent +{ + public static SHOW_THUMBNAIL: string = 'NE_SHOW_THUMBNAIL'; + public static HIDE_THUMBNAIL: string = 'NE_HIDE_THUMBNAIL'; + public static TOGGLE_THUMBNAIL: string = 'NE_TOGGLE_THUMBNAIL'; +} diff --git a/src/events/room-widgets/thumbnail/index.ts b/src/events/room-widgets/thumbnail/index.ts new file mode 100644 index 00000000..56f116d3 --- /dev/null +++ b/src/events/room-widgets/thumbnail/index.ts @@ -0,0 +1 @@ +export * from './RoomWidgetThumbnailEvent'; diff --git a/src/views/navigator/NavigatorMessageHandler.tsx b/src/views/navigator/NavigatorMessageHandler.tsx index 3303559c..f3a62300 100644 --- a/src/views/navigator/NavigatorMessageHandler.tsx +++ b/src/views/navigator/NavigatorMessageHandler.tsx @@ -1,4 +1,4 @@ -import { GenericErrorEvent, NavigatorCategoriesComposer, NavigatorCategoriesEvent, NavigatorHomeRoomEvent, NavigatorMetadataEvent, NavigatorSearchEvent, NavigatorSettingsComposer, RoomCreatedEvent, RoomDataParser, RoomDoorbellAcceptedEvent, RoomDoorbellEvent, RoomForwardEvent, RoomInfoComposer, RoomInfoEvent, RoomInfoOwnerEvent, UserInfoEvent } from 'nitro-renderer'; +import { GenericErrorEvent, NavigatorCategoriesComposer, NavigatorCategoriesEvent, NavigatorHomeRoomEvent, NavigatorMetadataEvent, NavigatorSearchEvent, NavigatorSettingsComposer, RoomCreatedEvent, RoomDataParser, RoomDoorbellAcceptedEvent, RoomDoorbellEvent, RoomForwardEvent, RoomInfoComposer, RoomInfoEvent, RoomInfoOwnerEvent, RoomSettingsUpdatedEvent, UserInfoEvent } from 'nitro-renderer'; import { FC, useCallback } from 'react'; import { GetRoomSessionManager, GetSessionDataManager } from '../../api'; import { VisitRoom } from '../../api/navigator/VisitRoom'; @@ -174,6 +174,13 @@ export const NavigatorMessageHandler: FC = props = }); }, [ dispatchNavigatorState ]); + const onRoomSettingsUpdatedEvent = useCallback((event: RoomSettingsUpdatedEvent) => + { + const parser = event.getParser(); + + SendMessageHook(new RoomInfoComposer(parser.roomId, false, false)); + }, []); + CreateMessageHook(UserInfoEvent, onUserInfoEvent); CreateMessageHook(RoomForwardEvent, onRoomForwardEvent); CreateMessageHook(RoomInfoOwnerEvent, onRoomInfoOwnerEvent); @@ -186,6 +193,7 @@ export const NavigatorMessageHandler: FC = props = CreateMessageHook(NavigatorCategoriesEvent, onNavigatorCategoriesEvent); CreateMessageHook(RoomCreatedEvent, onRoomCreatedEvent); CreateMessageHook(NavigatorHomeRoomEvent, onNavigatorHomeRoomEvent); + CreateMessageHook(RoomSettingsUpdatedEvent, onRoomSettingsUpdatedEvent); return null; } diff --git a/src/views/navigator/NavigatorView.tsx b/src/views/navigator/NavigatorView.tsx index 123b4044..3b4d5c52 100644 --- a/src/views/navigator/NavigatorView.tsx +++ b/src/views/navigator/NavigatorView.tsx @@ -13,6 +13,7 @@ import { initialNavigator, NavigatorActions, NavigatorReducer } from './reducers import { NavigatorRoomCreatorView } from './views/creator/NavigatorRoomCreatorView'; import { NavigatorRoomInfoView } from './views/room-info/NavigatorRoomInfoView'; import { NavigatorRoomLinkView } from './views/room-link/NavigatorRoomLinkView'; +import { NavigatorRoomSettingsView } from './views/room-settings/NavigatorRoomSettingsView'; import { NavigatorSearchResultSetView } from './views/search-result-set/NavigatorSearchResultSetView'; import { NavigatorSearchView } from './views/search/NavigatorSearchView'; @@ -124,6 +125,7 @@ export const NavigatorView: FC = props => } { isRoomInfoOpen && setRoomInfoOpen(false) } /> } { isRoomLinkOpen && setRoomLinkOpen(false) } /> } + ); } diff --git a/src/views/navigator/common/RoomSettingsData.ts b/src/views/navigator/common/RoomSettingsData.ts new file mode 100644 index 00000000..95837d6a --- /dev/null +++ b/src/views/navigator/common/RoomSettingsData.ts @@ -0,0 +1,95 @@ +export default class RoomSettingsData +{ + public roomId: number; + public roomName: string; + public roomOriginalName: string; + public roomDescription: string; + public categoryId: number; + public userCount: number; + public tags: string[]; + public tradeState: number; + public allowWalkthrough: boolean; + + public lockState: number; + public originalLockState: number; + public password: string; + public confirmPassword: string; + public allowPets: boolean; + public allowPetsEat: boolean; + + public usersWithRights: Map; + public friendsWithoutRights: Map; + + public hideWalls: boolean; + public wallThickness: number; + public floorThickness: number; + public chatBubbleMode: number; + public chatBubbleWeight: number; + public chatBubbleSpeed: number; + public chatFloodProtection: number; + public chatDistance: number; + + public muteState: number; + public kickState: number; + public banState: number; + public bannedUsers: Map; + public selectedUserToUnban: number; + + constructor() + { + this.roomId = 0; + this.roomName = null; + this.roomOriginalName = null; + this.roomDescription = null; + this.categoryId = 0; + this.userCount = 0; + this.tags = []; + this.tradeState = 0; + this.allowWalkthrough = false; + + this.lockState = 0; + this.originalLockState = 0; + this.password = null; + this.confirmPassword = null; + this.allowPets = false; + this.allowPetsEat = false; + + this.usersWithRights = new Map(); + this.friendsWithoutRights = new Map(); + + this.hideWalls = false; + this.wallThickness = 0; + this.floorThickness = 0; + this.chatBubbleMode = 0; + this.chatBubbleWeight = 0; + this.chatBubbleSpeed = 0; + this.chatFloodProtection = 0; + this.chatDistance = 0; + + this.muteState = 0; + this.kickState = 0; + this.banState = 0; + this.bannedUsers = new Map(); + this.selectedUserToUnban = 0; + } + + public selectUserToUnban(userId: number): void + { + if(this.selectedUserToUnban === userId) + { + this.selectedUserToUnban = 0; + } + else + { + this.selectedUserToUnban = userId; + } + } + + public get selectedUsernameToUnban(): string + { + if(this.selectedUserToUnban > 0) + return this.bannedUsers.get(this.selectedUserToUnban); + + return null; + } +} diff --git a/src/views/navigator/views/NavigatorViews.scss b/src/views/navigator/views/NavigatorViews.scss index 174445ad..91af623e 100644 --- a/src/views/navigator/views/NavigatorViews.scss +++ b/src/views/navigator/views/NavigatorViews.scss @@ -4,3 +4,4 @@ @import './search-result-item/NavigatorSearchResultItemView'; @import './room-info/NavigatorRoomInfoView'; @import './room-link/NavigatorRoomLinkView'; +@import './room-settings/NavigatorRoomSettingsView'; diff --git a/src/views/navigator/views/room-info/NavigatorRoomInfoView.tsx b/src/views/navigator/views/room-info/NavigatorRoomInfoView.tsx index d7c483aa..d927791d 100644 --- a/src/views/navigator/views/room-info/NavigatorRoomInfoView.tsx +++ b/src/views/navigator/views/room-info/NavigatorRoomInfoView.tsx @@ -1,8 +1,9 @@ import classNames from 'classnames'; -import { RoomMuteComposer, RoomStaffPickComposer, UserHomeRoomComposer } from 'nitro-renderer'; +import { RoomMuteComposer, RoomSettingsComposer, RoomStaffPickComposer, UserHomeRoomComposer } from 'nitro-renderer'; import { FC, useCallback, useEffect, useState } from 'react'; import { GetConfiguration } from '../../../../api'; import { NavigatorEvent } from '../../../../events'; +import { RoomWidgetThumbnailEvent } from '../../../../events/room-widgets/thumbnail'; import { dispatchUiEvent } from '../../../../hooks/events'; import { SendMessageHook } from '../../../../hooks/messages'; import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../layout'; @@ -61,12 +62,16 @@ export const NavigatorRoomInfoView: FC = props => case 'navigator_search_tag': return; case 'open_room_thumbnail_camera': + dispatchUiEvent(new RoomWidgetThumbnailEvent(RoomWidgetThumbnailEvent.TOGGLE_THUMBNAIL)); return; case 'open_group_info': return; case 'toggle_room_link': dispatchUiEvent(new NavigatorEvent(NavigatorEvent.TOGGLE_ROOM_LINK)); return; + case 'open_room_settings': + SendMessageHook(new RoomSettingsComposer(roomInfoData.enteredGuestRoom.roomId)); + return; case 'toggle_pick': setIsRoomPicked(value => !value); SendMessageHook(new RoomStaffPickComposer(roomInfoData.enteredGuestRoom.roomId)); @@ -115,7 +120,7 @@ export const NavigatorRoomInfoView: FC = props =>
{ roomInfoData.enteredGuestRoom.description }
- + processAction('open_room_thumbnail_camera') } /> { roomThumbnail && }
{ roomInfoData.enteredGuestRoom.habboGroupId > 0 &&
processAction('open_group_info') }> @@ -130,7 +135,7 @@ export const NavigatorRoomInfoView: FC = props => { LocalizeText('navigator.embed.caption') }
- + diff --git a/src/views/navigator/views/room-settings/NavigatorRoomSettingsView.scss b/src/views/navigator/views/room-settings/NavigatorRoomSettingsView.scss new file mode 100644 index 00000000..ac5adf97 --- /dev/null +++ b/src/views/navigator/views/room-settings/NavigatorRoomSettingsView.scss @@ -0,0 +1,3 @@ +.nitro-room-settings { + width: 400px; +} diff --git a/src/views/navigator/views/room-settings/NavigatorRoomSettingsView.tsx b/src/views/navigator/views/room-settings/NavigatorRoomSettingsView.tsx index a214d88e..c9fe09a0 100644 --- a/src/views/navigator/views/room-settings/NavigatorRoomSettingsView.tsx +++ b/src/views/navigator/views/room-settings/NavigatorRoomSettingsView.tsx @@ -1,6 +1,127 @@ -import { FC } from 'react'; +import { RoomSettingsEvent, SaveRoomSettingsComposer } from 'nitro-renderer'; +import { FC, useCallback, useState } from 'react'; +import { CreateMessageHook, SendMessageHook } from '../../../../hooks/messages'; +import { NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView } from '../../../../layout'; +import { LocalizeText } from '../../../../utils/LocalizeText'; +import RoomSettingsData from '../../common/RoomSettingsData'; +import { NavigatorRoomSettingsAccessTabView } from './views/tab-access/NavigatorRoomSettingsAccessTabView'; +import { NavigatorRoomSettingsBasicTabView } from './views/tab-basic/NavigatorRoomSettingsBasicTabView'; + +const TABS: string[] = [ + 'navigator.roomsettings.tab.1', + 'navigator.roomsettings.tab.2', + 'navigator.roomsettings.tab.3', + 'navigator.roomsettings.tab.4', + 'navigator.roomsettings.tab.5' +]; export const NavigatorRoomSettingsView: FC<{}> = props => { - return null; + const [ roomSettingsData, setRoomSettingsData ] = useState(null); + const [ currentTab, setCurrentTab ] = useState(TABS[0]); + + const updateSettings = useCallback((roomSettings: RoomSettingsData) => + { + console.log('update', roomSettings); + setRoomSettingsData(roomSettings); + }, [ setRoomSettingsData ]); + + const onRoomSettingsEvent = useCallback((event: RoomSettingsEvent) => + { + const parser = event.getParser(); + + const roomSettingsData = new RoomSettingsData(); + + roomSettingsData.roomId = parser.roomId; + roomSettingsData.roomName = parser.name; + roomSettingsData.roomOriginalName = parser.name; + roomSettingsData.roomDescription = parser.description; + roomSettingsData.categoryId = parser.categoryId; + roomSettingsData.userCount = parser.userCount; + roomSettingsData.tradeState = parser.tradeMode; + roomSettingsData.allowWalkthrough = parser.allowWalkthrough; + + roomSettingsData.lockState = parser.state; + roomSettingsData.originalLockState = parser.state; + roomSettingsData.allowPets = parser.allowPets; + + roomSettingsData.hideWalls = parser.hideWalls; + roomSettingsData.wallThickness = parser.thicknessWall; + roomSettingsData.floorThickness = parser.thicknessFloor; + roomSettingsData.chatBubbleMode = parser.chatSettings.mode; + roomSettingsData.chatBubbleWeight = parser.chatSettings.weight; + roomSettingsData.chatBubbleSpeed = parser.chatSettings.speed; + roomSettingsData.chatFloodProtection = parser.chatSettings.protection; + roomSettingsData.chatDistance = parser.chatSettings.distance; + + roomSettingsData.muteState = parser.moderationSettings.allowMute; + roomSettingsData.kickState = parser.moderationSettings.allowKick; + roomSettingsData.banState = parser.moderationSettings.allowBan; + + setRoomSettingsData(roomSettingsData); + }, []); + + CreateMessageHook(RoomSettingsEvent, onRoomSettingsEvent); + + const save = useCallback(() => + { + console.log('save', roomSettingsData) + const composer = new SaveRoomSettingsComposer( + roomSettingsData.roomId, + roomSettingsData.roomName, + roomSettingsData.roomDescription, + roomSettingsData.lockState, + roomSettingsData.password, + roomSettingsData.userCount, + roomSettingsData.categoryId, + roomSettingsData.tags.length, + roomSettingsData.tags, + roomSettingsData.tradeState, + roomSettingsData.allowPets, + roomSettingsData.allowPetsEat, + roomSettingsData.allowWalkthrough, + roomSettingsData.hideWalls, + roomSettingsData.wallThickness, + roomSettingsData.floorThickness, + roomSettingsData.muteState, + roomSettingsData.kickState, + roomSettingsData.banState, + roomSettingsData.chatBubbleMode, + roomSettingsData.chatBubbleWeight, + roomSettingsData.chatBubbleSpeed, + roomSettingsData.chatDistance, + roomSettingsData.chatFloodProtection + ); + + SendMessageHook(composer); + }, [ roomSettingsData ]); + + const processAction = useCallback((action: string) => + { + switch(action) + { + case 'close': + setRoomSettingsData(null); + setCurrentTab(TABS[0]); + return; + } + }, [ setRoomSettingsData ]); + + if(!roomSettingsData) return null; + + return ( + + processAction('close') } /> + + { TABS.map(tab => + { + return setCurrentTab(tab) }>{ LocalizeText(tab) } + }) } + + + { currentTab === TABS[0] && } + { currentTab === TABS[1] && } + + + ); }; diff --git a/src/views/navigator/views/room-settings/NavigatorRoomSettingsView.types.ts b/src/views/navigator/views/room-settings/NavigatorRoomSettingsView.types.ts new file mode 100644 index 00000000..52eded57 --- /dev/null +++ b/src/views/navigator/views/room-settings/NavigatorRoomSettingsView.types.ts @@ -0,0 +1,8 @@ +import RoomSettingsData from '../../common/RoomSettingsData'; + +export class NavigatorRoomSettingsTabViewProps +{ + roomSettingsData: RoomSettingsData; + setRoomSettingsData: (roomSettings: RoomSettingsData) => void; + onSave: () => void; +} diff --git a/src/views/navigator/views/room-settings/views/tab-access/NavigatorRoomSettingsAccessTabView.tsx b/src/views/navigator/views/room-settings/views/tab-access/NavigatorRoomSettingsAccessTabView.tsx new file mode 100644 index 00000000..579f2e55 --- /dev/null +++ b/src/views/navigator/views/room-settings/views/tab-access/NavigatorRoomSettingsAccessTabView.tsx @@ -0,0 +1,96 @@ +import { FC, useCallback } from 'react'; +import { LocalizeText } from '../../../../../../utils/LocalizeText'; +import RoomSettingsData from '../../../../common/RoomSettingsData'; +import { NavigatorRoomSettingsTabViewProps } from '../../NavigatorRoomSettingsView.types'; + +export const NavigatorRoomSettingsAccessTabView: FC = props => +{ + const { roomSettingsData = null, setRoomSettingsData = null, onSave = null } = props; + + const handleChange = useCallback((field: string, value: string | number | boolean) => + { + const roomSettings = ({...roomSettingsData} as RoomSettingsData); + let save = true; + + switch(field) + { + case 'lock_state': + roomSettings.lockState = Number(value); + + if(Number(value) === 3) save = false; + break; + case 'password': + roomSettings.password = String(value); + save = false; + break; + case 'confirm_password': + roomSettings.confirmPassword = String(value); + save = false; + break; + case 'allow_pets': + roomSettings.allowPets = Boolean(value); + break; + case 'allow_pets_eat': + roomSettings.allowPetsEat = Boolean(value); + break; + } + + setRoomSettingsData(roomSettings); + + if(save) onSave(); + }, [ roomSettingsData, setRoomSettingsData, onSave ]); + + const isPasswordValid = useCallback(() => + { + return (roomSettingsData.password && roomSettingsData.password.length > 0 && roomSettingsData.password === roomSettingsData.confirmPassword); + }, [ roomSettingsData ]); + + const trySave = useCallback(() => + { + if(isPasswordValid()) onSave(); + }, [ isPasswordValid, onSave ]); + + return ( + <> +
{ LocalizeText('navigator.roomsettings.doormode') }
+
+ handleChange('lock_state', 0) } /> + +
+
+ handleChange('lock_state', 1) } /> + +
+
+ handleChange('lock_state', 2) } /> + +
+
+ handleChange('lock_state', 3) } /> + +
+ { roomSettingsData.lockState === 3 && <> +
+ + handleChange('password', e.target.value) } onBlur={ trySave } placeholder="*****" /> +
+
+ + handleChange('confirm_password', e.target.value) } onBlur={ trySave } placeholder="*****" /> + { !isPasswordValid() && + { LocalizeText('navigator.roomsettings.invalidconfirm') } + } +
+ } +
{ LocalizeText('navigator.roomsettings.pets') }
+
+ handleChange('allow_pets', e.target.checked) } /> + +
+
+ handleChange('allow_pets_eat', e.target.checked) } /> + +
+ + ); +}; diff --git a/src/views/navigator/views/room-settings/views/tab-basic/NavigatorRoomSettingsBasicTabView.tsx b/src/views/navigator/views/room-settings/views/tab-basic/NavigatorRoomSettingsBasicTabView.tsx new file mode 100644 index 00000000..1a362e82 --- /dev/null +++ b/src/views/navigator/views/room-settings/views/tab-basic/NavigatorRoomSettingsBasicTabView.tsx @@ -0,0 +1,107 @@ +import { FC, useCallback, useEffect, useState } from 'react'; +import { LocalizeText } from '../../../../../../utils/LocalizeText'; +import RoomSettingsData from '../../../../common/RoomSettingsData'; +import { useNavigatorContext } from '../../../../context/NavigatorContext'; +import { NavigatorRoomSettingsTabViewProps } from '../../NavigatorRoomSettingsView.types'; + +export const NavigatorRoomSettingsBasicTabView: FC = props => +{ + const { roomSettingsData = null, setRoomSettingsData = null, onSave = null } = props; + + const { navigatorState = null } = useNavigatorContext(); + const { categories = null } = navigatorState; + + const [ maxVisitorsList, setMaxVisitorsList ] = useState(null); + + useEffect(() => + { + if(!maxVisitorsList) + { + const list = []; + + for(let i = 10; i <= 100; i = i + 10) + { + list.push(i); + } + + setMaxVisitorsList(list); + } + }, [ maxVisitorsList ]); + + const handleChange = useCallback((field: string, value: string | number | boolean) => + { + const roomSettings = ({...roomSettingsData} as RoomSettingsData); + let save = true; + + switch(field) + { + case 'name': + roomSettings.roomName = String(value); + save = false; + break; + case 'description': + roomSettings.roomDescription = String(value); + save = false; + break; + case 'category': + roomSettings.categoryId = Number(value); + break; + case 'max_visitors': + roomSettings.userCount = Number(value); + break; + case 'trade_state': + roomSettings.tradeState = Number(value); + break; + case 'allow_walkthrough': + roomSettings.allowWalkthrough = Boolean(value); + break; + } + + setRoomSettingsData(roomSettings); + + if(save) onSave(); + }, [ roomSettingsData, setRoomSettingsData, onSave ]); + + return ( + <> +
+ + handleChange('name', e.target.value) } onBlur={ onSave } /> +
+
+ + handleChange('description', e.target.value) } onBlur={ () => onSave() } /> +
+
+ + +
+
+ + +
+
+ + +
+
+ handleChange('allow_walkthrough', e.target.checked) } /> + +
+ + ); +}; diff --git a/src/views/room/widgets/room-thumbnail/RoomThumbnailWidgetView.tsx b/src/views/room/widgets/room-thumbnail/RoomThumbnailWidgetView.tsx index 90d7fb63..29b94fed 100644 --- a/src/views/room/widgets/room-thumbnail/RoomThumbnailWidgetView.tsx +++ b/src/views/room/widgets/room-thumbnail/RoomThumbnailWidgetView.tsx @@ -1,4 +1,6 @@ import { FC, useCallback, useState } from 'react'; +import { RoomWidgetThumbnailEvent } from '../../../../events/room-widgets/thumbnail'; +import { useUiEvent } from '../../../../hooks/events'; import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../layout'; import { LocalizeText } from '../../../../utils/LocalizeText'; import { RoomThumbnailWidgetViewProps } from './RoomThumbnailView.types'; @@ -11,6 +13,30 @@ export const RoomThumbnailWidgetView: FC = props = const [ isBuilderVisible, setIsBuilderVisible ] = useState(false); const [ isCameraVisible, setIsCameraVisible ] = useState(false); + const onNitroEvent = useCallback((event: RoomWidgetThumbnailEvent) => + { + switch(event.type) + { + case RoomWidgetThumbnailEvent.SHOW_THUMBNAIL: + setIsSelectorVisible(true); + return; + case RoomWidgetThumbnailEvent.HIDE_THUMBNAIL: + setIsSelectorVisible(false); + setIsBuilderVisible(false); + setIsCameraVisible(false); + return; + case RoomWidgetThumbnailEvent.TOGGLE_THUMBNAIL: + setIsSelectorVisible(value => !value); + setIsBuilderVisible(false); + setIsCameraVisible(false); + return; + } + }, []); + + useUiEvent(RoomWidgetThumbnailEvent.SHOW_THUMBNAIL, onNitroEvent); + useUiEvent(RoomWidgetThumbnailEvent.HIDE_THUMBNAIL, onNitroEvent); + useUiEvent(RoomWidgetThumbnailEvent.TOGGLE_THUMBNAIL, onNitroEvent); + const handleAction = useCallback((action: string) => { switch(action)