diff --git a/package-lock.json b/package-lock.json index 69072740..2b0533bd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5919,6 +5919,11 @@ "wrap-ansi": "^6.2.0" } }, + "clsx": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.1.1.tgz", + "integrity": "sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==" + }, "co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -14876,6 +14881,19 @@ "prop-types": "^15.6.2" } }, + "react-virtualized": { + "version": "9.22.3", + "resolved": "https://registry.npmjs.org/react-virtualized/-/react-virtualized-9.22.3.tgz", + "integrity": "sha512-MKovKMxWTcwPSxE1kK1HcheQTWfuCxAuBoSTf2gwyMM21NdX/PXUhnoP8Uc5dRKd+nKm8v41R36OellhdCpkrw==", + "requires": { + "@babel/runtime": "^7.7.2", + "clsx": "^1.0.4", + "dom-helpers": "^5.1.3", + "loose-envify": "^1.4.0", + "prop-types": "^15.7.2", + "react-lifecycles-compat": "^3.0.4" + } + }, "read-pkg": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", diff --git a/package.json b/package.json index 116274d8..ae0751c7 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "react-scripts": "4.0.3", "react-slider": "^1.3.1", "react-transition-group": "^4.4.2", + "react-virtualized": "^9.22.3", "typescript": "^4.3.5", "web-vitals": "^1.1.2" }, diff --git a/src/App.tsx b/src/App.tsx index 8a9c2f2c..6b759d0e 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,4 +1,4 @@ -import { ConfigurationEvent, LegacyExternalInterface, Nitro, NitroCommunicationDemoEvent, NitroEvent, NitroLocalizationEvent, RoomEngineEvent, WebGL } from '@nitrots/nitro-renderer'; +import { ConfigurationEvent, LegacyExternalInterface, Nitro, NitroCommunicationDemoEvent, NitroEvent, NitroLocalizationEvent, NitroVersion, RoomEngineEvent, WebGL } from '@nitrots/nitro-renderer'; import { FC, useCallback, useState } from 'react'; import { GetCommunication, GetConfiguration, GetNitroInstance } from './api'; import { useConfigurationEvent } from './hooks/events/core/configuration/configuration-event'; @@ -17,7 +17,11 @@ export const App: FC<{}> = props => //@ts-ignore if(!NitroConfig) throw new Error('NitroConfig is not defined!'); - if(!GetNitroInstance()) Nitro.bootstrap(); + if(!GetNitroInstance()) + { + NitroVersion.UI_VERSION = '2.0.0'; + Nitro.bootstrap(); + } const getPreloadAssetUrls = useCallback(() => { diff --git a/src/api/navigator/VisitRoom.ts b/src/api/navigator/VisitRoom.ts deleted file mode 100644 index 9448f66d..00000000 --- a/src/api/navigator/VisitRoom.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { GetRoomSessionManager } from '../nitro'; - -export function VisitRoom(roomId: number, password: string = null): void -{ - GetRoomSessionManager().createSession(roomId, password); -} diff --git a/src/api/nitro/session/CreateRoomSession.ts b/src/api/nitro/session/CreateRoomSession.ts new file mode 100644 index 00000000..3be6a8ab --- /dev/null +++ b/src/api/nitro/session/CreateRoomSession.ts @@ -0,0 +1,6 @@ +import { GetRoomSessionManager } from './GetRoomSessionManager'; + +export function CreateRoomSession(roomId: number, password: string = null): void +{ + GetRoomSessionManager().createSession(roomId, password); +} diff --git a/src/api/nitro/session/GoToDesktop.ts b/src/api/nitro/session/GoToDesktop.ts new file mode 100644 index 00000000..4617043c --- /dev/null +++ b/src/api/nitro/session/GoToDesktop.ts @@ -0,0 +1,7 @@ +import { DesktopViewComposer } from '@nitrots/nitro-renderer'; +import { SendMessageHook } from '../../../hooks'; + +export function GoToDesktop(): void +{ + SendMessageHook(new DesktopViewComposer()); +} diff --git a/src/api/nitro/session/index.ts b/src/api/nitro/session/index.ts index 31861a96..5409266d 100644 --- a/src/api/nitro/session/index.ts +++ b/src/api/nitro/session/index.ts @@ -1,4 +1,5 @@ export * from './CanManipulateFurniture'; +export * from './CreateRoomSession'; export * from './GetCanStandUp'; export * from './GetCanUseExpression'; export * from './GetClubMemberLevel'; @@ -9,6 +10,7 @@ export * from './GetProductDataForLocalization'; export * from './GetRoomSession'; export * from './GetRoomSessionManager'; export * from './GetSessionDataManager'; +export * from './GoToDesktop'; export * from './HasHabboClub'; export * from './HasHabboVip'; export * from './IsOwnerOfFurniture'; diff --git a/src/events/navigator/UpdateDoorStateEvent.ts b/src/events/navigator/UpdateDoorStateEvent.ts new file mode 100644 index 00000000..0c4bdfca --- /dev/null +++ b/src/events/navigator/UpdateDoorStateEvent.ts @@ -0,0 +1,27 @@ +import { NitroEvent, RoomDataParser } from '@nitrots/nitro-renderer'; + +export class UpdateDoorStateEvent extends NitroEvent +{ + public static START_DOORBELL: string = 'UDSE_START_DOORBELL'; + public static START_PASSWORD: string = 'UDSE_START_PASSWORD'; + public static STATE_PENDING_SERVER: string = 'UDSE_STATE_PENDING_SERVER'; + public static UPDATE_STATE: string = 'UDSE_UPDATE_STATE'; + public static STATE_WAITING: string = 'UDSE_STATE_WAITING'; + public static STATE_NO_ANSWER: string = 'UDSE_STATE_NO_ANSWER'; + public static STATE_WRONG_PASSWORD: string = 'UDSE_STATE_WRONG_PASSWORD'; + public static STATE_ACCEPTED: string = 'UDSE_STATE_ACCEPTED'; + + private _roomData: RoomDataParser + + constructor(type: string, roomData: RoomDataParser = null) + { + super(type); + + this._roomData = roomData; + } + + public get roomData(): RoomDataParser + { + return this._roomData; + } +} diff --git a/src/events/navigator/index.ts b/src/events/navigator/index.ts index c2e7371c..892c758a 100644 --- a/src/events/navigator/index.ts +++ b/src/events/navigator/index.ts @@ -1 +1,2 @@ export * from './NavigatorEvent'; +export * from './UpdateDoorStateEvent'; diff --git a/src/events/room-widgets/choosers/RoomObjectItem.ts b/src/events/room-widgets/choosers/RoomObjectItem.ts new file mode 100644 index 00000000..d11fc5a8 --- /dev/null +++ b/src/events/room-widgets/choosers/RoomObjectItem.ts @@ -0,0 +1,28 @@ +export class RoomObjectItem +{ + private readonly _id: number; + private readonly _category: number; + private readonly _name: string; + + constructor(id: number, category: number, name: string) + { + this._id = id; + this._category = category; + this._name = name; + } + + public get id(): number + { + return this._id; + } + + public get category(): number + { + return this._category; + } + + public get name(): string + { + return this._name; + } +} diff --git a/src/events/room-widgets/choosers/RoomWidgetChooserContentEvent.ts b/src/events/room-widgets/choosers/RoomWidgetChooserContentEvent.ts new file mode 100644 index 00000000..aabb254e --- /dev/null +++ b/src/events/room-widgets/choosers/RoomWidgetChooserContentEvent.ts @@ -0,0 +1,29 @@ +import { RoomWidgetUpdateEvent } from '../../../views/room/events'; +import { RoomObjectItem } from './RoomObjectItem'; + +export class RoomWidgetChooserContentEvent extends RoomWidgetUpdateEvent +{ + public static USER_CHOOSER_CONTENT: string = 'RWCCE_USER_CHOOSER_CONTENT'; + public static FURNI_CHOOSER_CONTENT: string = 'RWCCE_FURNI_CHOOSER_CONTENT'; + + private _items: RoomObjectItem[]; + private _isAnyRoomController: boolean; + + constructor(type: string, items: RoomObjectItem[], isAnyRoomController: boolean = false) + { + super(type); + + this._items = items.slice(); + this._isAnyRoomController = isAnyRoomController; + } + + public get items(): RoomObjectItem[] + { + return this._items; + } + + public get isAnyRoomController(): boolean + { + return this._isAnyRoomController; + } +} diff --git a/src/views/navigator/NavigatorMessageHandler.tsx b/src/views/navigator/NavigatorMessageHandler.tsx index 964007e6..f2e4e019 100644 --- a/src/views/navigator/NavigatorMessageHandler.tsx +++ b/src/views/navigator/NavigatorMessageHandler.tsx @@ -1,7 +1,8 @@ -import { GenericErrorEvent, NavigatorCategoriesComposer, NavigatorCategoriesEvent, NavigatorHomeRoomEvent, NavigatorMetadataEvent, NavigatorSearchEvent, NavigatorSettingsComposer, RoomCreatedEvent, RoomDataParser, RoomDoorbellAcceptedEvent, RoomDoorbellEvent, RoomForwardEvent, RoomInfoComposer, RoomInfoEvent, RoomInfoOwnerEvent, RoomSettingsUpdatedEvent, UserInfoEvent } from '@nitrots/nitro-renderer'; +import { GenericErrorEvent, NavigatorCategoriesComposer, NavigatorCategoriesEvent, NavigatorHomeRoomEvent, NavigatorMetadataEvent, NavigatorSearchEvent, NavigatorSettingsComposer, RoomCreatedEvent, RoomDataParser, RoomDoorbellAcceptedEvent, RoomDoorbellEvent, RoomDoorbellRejectedEvent, RoomForwardEvent, RoomInfoComposer, RoomInfoEvent, RoomInfoOwnerEvent, RoomSettingsUpdatedEvent, UserInfoEvent } from '@nitrots/nitro-renderer'; import { FC, useCallback } from 'react'; -import { GetRoomSessionManager, GetSessionDataManager } from '../../api'; -import { VisitRoom } from '../../api/navigator/VisitRoom'; +import { CreateRoomSession, GetSessionDataManager } from '../../api'; +import { UpdateDoorStateEvent } from '../../events'; +import { dispatchUiEvent } from '../../hooks'; import { CreateMessageHook, SendMessageHook } from '../../hooks/messages/message-event'; import { useNavigatorContext } from './context/NavigatorContext'; import { NavigatorMessageHandlerProps } from './NavigatorMessageHandler.types'; @@ -65,13 +66,15 @@ export const NavigatorMessageHandler: FC = props = switch(parser.data.doorMode) { case RoomDataParser.DOORBELL_STATE: + dispatchUiEvent(new UpdateDoorStateEvent(UpdateDoorStateEvent.START_DOORBELL, parser.data)); + return; case RoomDataParser.PASSWORD_STATE: - //showLock(); + dispatchUiEvent(new UpdateDoorStateEvent(UpdateDoorStateEvent.START_PASSWORD, parser.data)); return; } } - GetRoomSessionManager().createSession(parser.data.roomId); + CreateRoomSession(parser.data.roomId); } else { @@ -91,32 +94,42 @@ export const NavigatorMessageHandler: FC = props = { const parser = event.getParser(); - // if(!parser.userName || (parser.userName.length === 0)) - // { - // showLock(NavigatorLockViewStage.WAITING); - // } + if(!parser.userName || (parser.userName.length === 0)) + { + dispatchUiEvent(new UpdateDoorStateEvent(UpdateDoorStateEvent.STATE_WAITING)); + } }, []); const onRoomDoorbellAcceptedEvent = useCallback((event: RoomDoorbellAcceptedEvent) => { const parser = event.getParser(); - // if(!parser.userName || (parser.userName.length === 0)) - // { - // hideLock(); - // } + if(!parser.userName || (parser.userName.length === 0)) + { + dispatchUiEvent(new UpdateDoorStateEvent(UpdateDoorStateEvent.STATE_ACCEPTED)); + } + }, []); + + const onRoomDoorbellRejectedEvent = useCallback((event: RoomDoorbellRejectedEvent) => + { + const parser = event.getParser(); + + if(!parser.userName || (parser.userName.length === 0)) + { + dispatchUiEvent(new UpdateDoorStateEvent(UpdateDoorStateEvent.STATE_NO_ANSWER)); + } }, []); const onGenericErrorEvent = useCallback((event: GenericErrorEvent) => { const parser = event.getParser(); - // switch(parser.errorCode) - // { - // case -100002: - // showLock(NavigatorLockViewStage.FAILED); - // break; - // } + switch(parser.errorCode) + { + case -100002: + dispatchUiEvent(new UpdateDoorStateEvent(UpdateDoorStateEvent.STATE_WRONG_PASSWORD)); + break; + } }, []); const onNavigatorMetadataEvent = useCallback((event: NavigatorMetadataEvent) => @@ -159,7 +172,7 @@ export const NavigatorMessageHandler: FC = props = { const parser = event.getParser(); - VisitRoom(parser.roomId); + CreateRoomSession(parser.roomId); }, []); const onNavigatorHomeRoomEvent = useCallback((event: NavigatorHomeRoomEvent) => @@ -187,6 +200,7 @@ export const NavigatorMessageHandler: FC = props = CreateMessageHook(RoomInfoEvent, onRoomInfoEvent); CreateMessageHook(RoomDoorbellEvent, onRoomDoorbellEvent); CreateMessageHook(RoomDoorbellAcceptedEvent, onRoomDoorbellAcceptedEvent); + CreateMessageHook(RoomDoorbellRejectedEvent, onRoomDoorbellRejectedEvent); CreateMessageHook(GenericErrorEvent, onGenericErrorEvent); CreateMessageHook(NavigatorMetadataEvent, onNavigatorMetadataEvent); CreateMessageHook(NavigatorSearchEvent, onNavigatorSearchEvent); diff --git a/src/views/navigator/NavigatorView.scss b/src/views/navigator/NavigatorView.scss index e17312aa..d29e353b 100644 --- a/src/views/navigator/NavigatorView.scss +++ b/src/views/navigator/NavigatorView.scss @@ -8,4 +8,22 @@ } } +.nitro-navigator-doorbell { + width: 250px; + + .content-area { + min-height: 143px; + height: 143px; + } +} + +.nitro-navigator-password { + width: 250px; + + .content-area { + min-height: 218px; + height: 218px; + } +} + @import './views/NavigatorViews'; diff --git a/src/views/navigator/NavigatorView.tsx b/src/views/navigator/NavigatorView.tsx index 62a60ab9..320e1d11 100644 --- a/src/views/navigator/NavigatorView.tsx +++ b/src/views/navigator/NavigatorView.tsx @@ -1,8 +1,8 @@ -import { ILinkEventTracker, NavigatorInitComposer, NavigatorSearchComposer, RoomSessionEvent } from '@nitrots/nitro-renderer'; -import { FC, useCallback, useEffect, useReducer, useState } from 'react'; -import { AddEventLinkTracker, RemoveLinkEventTracker } from '../../api'; +import { ILinkEventTracker, NavigatorInitComposer, NavigatorSearchComposer, RoomDataParser, RoomSessionEvent } from '@nitrots/nitro-renderer'; +import { FC, useCallback, useEffect, useMemo, useReducer, useState } from 'react'; +import { AddEventLinkTracker, GoToDesktop, RemoveLinkEventTracker } from '../../api'; import { TryVisitRoom } from '../../api/navigator/TryVisitRoom'; -import { NavigatorEvent } from '../../events'; +import { NavigatorEvent, UpdateDoorStateEvent } from '../../events'; import { useRoomSessionManagerEvent } from '../../hooks/events/nitro/session/room-session-manager-event'; import { useUiEvent } from '../../hooks/events/ui/ui-event'; import { SendMessageHook } from '../../hooks/messages/message-event'; @@ -13,8 +13,10 @@ import { NavigatorMessageHandler } from './NavigatorMessageHandler'; import { NavigatorViewProps } from './NavigatorView.types'; import { initialNavigator, NavigatorActions, NavigatorReducer } from './reducers/NavigatorReducer'; import { NavigatorRoomCreatorView } from './views/creator/NavigatorRoomCreatorView'; +import { NavigatorRoomDoorbellView } from './views/room-doorbell/NavigatorRoomDoorbellView'; import { NavigatorRoomInfoView } from './views/room-info/NavigatorRoomInfoView'; import { NavigatorRoomLinkView } from './views/room-link/NavigatorRoomLinkView'; +import { NavigatorRoomPasswordView } from './views/room-password/NavigatorRoomPasswordView'; import { NavigatorRoomSettingsView } from './views/room-settings/NavigatorRoomSettingsView'; import { NavigatorSearchResultSetView } from './views/search-result-set/NavigatorSearchResultSetView'; import { NavigatorSearchView } from './views/search/NavigatorSearchView'; @@ -25,6 +27,7 @@ export const NavigatorView: FC = props => const [ isCreatorOpen, setCreatorOpen ] = useState(false); const [ isRoomInfoOpen, setRoomInfoOpen ] = useState(false); const [ isRoomLinkOpen, setRoomLinkOpen ] = useState(false); + const [ pendingDoorState, setPendingDoorState ] = useState<{ roomData: RoomDataParser, state: string }>(null); const [ navigatorState, dispatchNavigatorState ] = useReducer(NavigatorReducer, initialNavigator); const { needsNavigatorUpdate = false, topLevelContext = null, topLevelContexts = null } = navigatorState; @@ -56,6 +59,49 @@ export const NavigatorView: FC = props => useUiEvent(NavigatorEvent.TOGGLE_ROOM_INFO, onNavigatorEvent); useUiEvent(NavigatorEvent.TOGGLE_ROOM_LINK, onNavigatorEvent); + const onUpdateDoorStateEvent = useCallback((event: UpdateDoorStateEvent) => + { + switch(event.type) + { + case UpdateDoorStateEvent.START_DOORBELL: + setPendingDoorState({ roomData: event.roomData, state: event.type }); + return; + case UpdateDoorStateEvent.START_PASSWORD: + setPendingDoorState({ roomData: event.roomData, state: event.type }); + return; + case UpdateDoorStateEvent.STATE_WAITING: + setPendingDoorState(prevValue => + { + return { roomData: prevValue.roomData, state: event.type } + }); + return; + case UpdateDoorStateEvent.STATE_NO_ANSWER: + setPendingDoorState(prevValue => + { + if(prevValue.state === UpdateDoorStateEvent.STATE_WAITING) GoToDesktop(); + + return { roomData: prevValue.roomData, state: event.type } + }); + return; + case UpdateDoorStateEvent.STATE_WRONG_PASSWORD: + setPendingDoorState(prevValue => + { + return { roomData: prevValue.roomData, state: event.type } + }); + return; + case UpdateDoorStateEvent.STATE_ACCEPTED: + setPendingDoorState(null); + return; + } + }, []); + + useUiEvent(UpdateDoorStateEvent.START_DOORBELL, onUpdateDoorStateEvent); + useUiEvent(UpdateDoorStateEvent.START_PASSWORD, onUpdateDoorStateEvent); + useUiEvent(UpdateDoorStateEvent.STATE_WAITING, onUpdateDoorStateEvent); + useUiEvent(UpdateDoorStateEvent.STATE_NO_ANSWER, onUpdateDoorStateEvent); + useUiEvent(UpdateDoorStateEvent.STATE_WRONG_PASSWORD, onUpdateDoorStateEvent); + useUiEvent(UpdateDoorStateEvent.STATE_ACCEPTED, onUpdateDoorStateEvent); + const onRoomSessionEvent = useCallback((event: RoomSessionEvent) => { switch(event.type) @@ -102,6 +148,18 @@ export const NavigatorView: FC = props => } }, []); + const closePendingDoorState = useCallback((state: string) => + { + if(state !== null) + { + setPendingDoorState(prevValue => + { + return { roomData: prevValue.roomData, state }; + }); + } + else setPendingDoorState(null); + }, []); + useEffect(() => { const linkTracker: ILinkEventTracker = { @@ -135,9 +193,28 @@ export const NavigatorView: FC = props => sendSearch('', topLevelContexts[0].code); }, [ topLevelContexts, sendSearch ]); + const getRoomDoorState = useMemo(() => + { + if(!pendingDoorState) return null; + + switch(pendingDoorState.state) + { + case UpdateDoorStateEvent.START_DOORBELL: + case UpdateDoorStateEvent.STATE_WAITING: + case UpdateDoorStateEvent.STATE_NO_ANSWER: + return ; + case UpdateDoorStateEvent.START_PASSWORD: + case UpdateDoorStateEvent.STATE_WRONG_PASSWORD: + return ; + } + + return null; + }, [ pendingDoorState, closePendingDoorState ]); + return ( + { getRoomDoorState } { isVisible && setIsVisible(false) } /> diff --git a/src/views/navigator/views/room-doorbell/NavigatorRoomDoorbellView.tsx b/src/views/navigator/views/room-doorbell/NavigatorRoomDoorbellView.tsx new file mode 100644 index 00000000..7bb331b6 --- /dev/null +++ b/src/views/navigator/views/room-doorbell/NavigatorRoomDoorbellView.tsx @@ -0,0 +1,43 @@ +import { FC, useCallback } from 'react'; +import { CreateRoomSession, GoToDesktop } from '../../../../api'; +import { UpdateDoorStateEvent } from '../../../../events'; +import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../layout'; +import { LocalizeText } from '../../../../utils'; +import { NavigatorRoomDoorbellViewProps } from './NavigatorRoomDoorbellView.types'; + +export const NavigatorRoomDoorbellView: FC = props => +{ + const { roomData = null, state = null, onClose = null } = props; + + const close = useCallback(() => + { + if(state === UpdateDoorStateEvent.STATE_WAITING) GoToDesktop(); + + onClose(null); + }, [ state, onClose ]); + + const ring = useCallback(() => + { + if(!roomData) return; + + CreateRoomSession(roomData.roomId); + + onClose(UpdateDoorStateEvent.STATE_PENDING_SERVER); + }, [ roomData, onClose ]); + + return ( + + + + { roomData && { roomData.roomName } } + { (state === UpdateDoorStateEvent.START_DOORBELL) && { LocalizeText('navigator.doorbell.info') } } + { (state === UpdateDoorStateEvent.STATE_WAITING) && { LocalizeText('navigator.doorbell.waiting') } } + { (state === UpdateDoorStateEvent.STATE_NO_ANSWER) && { LocalizeText('navigator.doorbell.no.answer') } } +
+ { (state === UpdateDoorStateEvent.START_DOORBELL) && } + +
+
+
+ ); +} diff --git a/src/views/navigator/views/room-doorbell/NavigatorRoomDoorbellView.types.ts b/src/views/navigator/views/room-doorbell/NavigatorRoomDoorbellView.types.ts new file mode 100644 index 00000000..f471bd21 --- /dev/null +++ b/src/views/navigator/views/room-doorbell/NavigatorRoomDoorbellView.types.ts @@ -0,0 +1,8 @@ +import { RoomDataParser } from '@nitrots/nitro-renderer'; + +export interface NavigatorRoomDoorbellViewProps +{ + roomData: RoomDataParser; + state: string; + onClose: (state: string) => void; +} diff --git a/src/views/navigator/views/room-password/NavigatorRoomPasswordView.tsx b/src/views/navigator/views/room-password/NavigatorRoomPasswordView.tsx new file mode 100644 index 00000000..247d0f71 --- /dev/null +++ b/src/views/navigator/views/room-password/NavigatorRoomPasswordView.tsx @@ -0,0 +1,45 @@ +import { FC, useCallback, useState } from 'react'; +import { CreateRoomSession } from '../../../../api'; +import { UpdateDoorStateEvent } from '../../../../events'; +import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../layout'; +import { LocalizeText } from '../../../../utils'; +import { NavigatorRoomPasswordViewProps } from './NavigatorRoomPasswordView.types'; + +export const NavigatorRoomPasswordView: FC = props => +{ + const { roomData = null, state = null, onClose = null } = props; + const [ password, setPassword ] = useState(''); + + const close = useCallback(() => + { + onClose(null); + }, [ onClose ]); + + const tryEntering = useCallback(() => + { + if(!roomData) return; + + CreateRoomSession(roomData.roomId, password); + + onClose(UpdateDoorStateEvent.STATE_PENDING_SERVER); + }, [ roomData, password, onClose ]); + + return ( + + + + { roomData && { roomData.roomName } } + { (state === UpdateDoorStateEvent.START_PASSWORD) && { LocalizeText('navigator.password.info') } } + { (state === UpdateDoorStateEvent.STATE_WRONG_PASSWORD) && { LocalizeText('navigator.password.retryinfo') } } +
+ + setPassword(event.target.value) } /> +
+
+ + +
+
+
+ ); +} diff --git a/src/views/navigator/views/room-password/NavigatorRoomPasswordView.types.ts b/src/views/navigator/views/room-password/NavigatorRoomPasswordView.types.ts new file mode 100644 index 00000000..4d1ea7d2 --- /dev/null +++ b/src/views/navigator/views/room-password/NavigatorRoomPasswordView.types.ts @@ -0,0 +1,8 @@ +import { RoomDataParser } from '@nitrots/nitro-renderer'; + +export interface NavigatorRoomPasswordViewProps +{ + roomData: RoomDataParser; + state: string; + onClose: (state: string) => void; +} diff --git a/src/views/navigator/views/search-result-item/NavigatorSearchResultItemView.tsx b/src/views/navigator/views/search-result-item/NavigatorSearchResultItemView.tsx index e38eebb1..946b9ed8 100644 --- a/src/views/navigator/views/search-result-item/NavigatorSearchResultItemView.tsx +++ b/src/views/navigator/views/search-result-item/NavigatorSearchResultItemView.tsx @@ -1,7 +1,10 @@ import { RoomDataParser } from '@nitrots/nitro-renderer'; import classNames from 'classnames'; import { FC, MouseEvent } from 'react'; +import { CreateRoomSession, GetSessionDataManager } from '../../../../api'; import { TryVisitRoom } from '../../../../api/navigator/TryVisitRoom'; +import { UpdateDoorStateEvent } from '../../../../events'; +import { dispatchUiEvent } from '../../../../hooks'; import { NavigatorSearchResultItemViewProps } from './NavigatorSearchResultItemView.types'; export const NavigatorSearchResultItemView: FC = props => @@ -38,7 +41,27 @@ export const NavigatorSearchResultItemView: FC { roomData.habboGroupId > 0 && } { roomData.doorMode !== RoomDataParser.OPEN_STATE && - + } diff --git a/src/views/room/RoomView.tsx b/src/views/room/RoomView.tsx index a82d4b9a..8b36f254 100644 --- a/src/views/room/RoomView.tsx +++ b/src/views/room/RoomView.tsx @@ -7,9 +7,12 @@ import { GetRoomEngine } from '../../api/nitro/room/GetRoomEngine'; import { RoomContextProvider } from './context/RoomContext'; import { RoomWidgetUpdateRoomViewEvent } from './events/RoomWidgetUpdateRoomViewEvent'; import { IRoomWidgetHandlerManager, RoomWidgetAvatarInfoHandler, RoomWidgetChatHandler, RoomWidgetChatInputHandler, RoomWidgetHandlerManager, RoomWidgetInfostandHandler } from './handlers'; +import { DoorbellWidgetHandler } from './handlers/DoorbellWidgetHandler'; +import { FurniChooserWidgetHandler } from './handlers/FurniChooserWidgetHandler'; import { FurnitureContextMenuWidgetHandler } from './handlers/FurnitureContextMenuWidgetHandler'; import { FurnitureCustomStackHeightWidgetHandler } from './handlers/FurnitureCustomStackHeightWidgetHandler'; import { RoomWidgetRoomToolsHandler } from './handlers/RoomWidgetRoomToolsHandler'; +import { UserChooserWidgetHandler } from './handlers/UserChooserWidgetHandler'; import { RoomColorView } from './RoomColorView'; import { RoomViewProps } from './RoomView.types'; import { RoomWidgetsView } from './widgets/RoomWidgetsView'; @@ -44,6 +47,9 @@ export const RoomView: FC = props => widgetHandlerManager.registerHandler(new RoomWidgetChatHandler()); widgetHandlerManager.registerHandler(new FurnitureContextMenuWidgetHandler()); widgetHandlerManager.registerHandler(new FurnitureCustomStackHeightWidgetHandler()); + widgetHandlerManager.registerHandler(new FurniChooserWidgetHandler()); + widgetHandlerManager.registerHandler(new UserChooserWidgetHandler()); + widgetHandlerManager.registerHandler(new DoorbellWidgetHandler()); setWidgetHandler(widgetHandlerManager); diff --git a/src/views/room/events/RoomWidgetDoorbellEvent.ts b/src/views/room/events/RoomWidgetDoorbellEvent.ts new file mode 100644 index 00000000..9bad7fa4 --- /dev/null +++ b/src/views/room/events/RoomWidgetDoorbellEvent.ts @@ -0,0 +1,22 @@ +import { RoomWidgetUpdateEvent } from './RoomWidgetUpdateEvent'; + +export class RoomWidgetDoorbellEvent extends RoomWidgetUpdateEvent +{ + public static RINGING: string = 'RWDE_RINGING'; + public static REJECTED: string = 'RWDE_REJECTED'; + public static ACCEPTED: string = 'RWDE_ACCEPTED'; + + private _userName: string = ''; + + constructor(type: string, userName: string) + { + super(type); + + this._userName = userName; + } + + public get userName(): string + { + return this._userName; + } +} diff --git a/src/views/room/events/index.ts b/src/views/room/events/index.ts index 60f8d0c8..4440c0af 100644 --- a/src/views/room/events/index.ts +++ b/src/views/room/events/index.ts @@ -1,4 +1,5 @@ export * from './RoomWidgetAvatarInfoEvent'; +export * from './RoomWidgetDoorbellEvent'; export * from './RoomWidgetFloodControlEvent'; export * from './RoomWidgetObjectNameEvent'; export * from './RoomWidgetRoomEngineUpdateEvent'; diff --git a/src/views/room/handlers/DoorbellWidgetHandler.ts b/src/views/room/handlers/DoorbellWidgetHandler.ts new file mode 100644 index 00000000..b2b69ee9 --- /dev/null +++ b/src/views/room/handlers/DoorbellWidgetHandler.ts @@ -0,0 +1,60 @@ +import { NitroEvent, RoomSessionDoorbellEvent, RoomWidgetEnum } from '@nitrots/nitro-renderer'; +import { RoomWidgetDoorbellEvent, RoomWidgetUpdateEvent } from '../events'; +import { RoomWidgetLetUserInMessage, RoomWidgetMessage } from '../messages'; +import { RoomWidgetHandler } from './RoomWidgetHandler'; + +export class DoorbellWidgetHandler extends RoomWidgetHandler +{ + public processEvent(event: NitroEvent): void + { + const doorbellEvent = (event as RoomSessionDoorbellEvent); + + switch(event.type) + { + case RoomSessionDoorbellEvent.DOORBELL: + this.container.eventDispatcher.dispatchEvent(new RoomWidgetDoorbellEvent(RoomWidgetDoorbellEvent.RINGING, doorbellEvent.userName)); + return; + case RoomSessionDoorbellEvent.RSDE_REJECTED: + this.container.eventDispatcher.dispatchEvent(new RoomWidgetDoorbellEvent(RoomWidgetDoorbellEvent.REJECTED, doorbellEvent.userName)); + return; + case RoomSessionDoorbellEvent.RSDE_ACCEPTED: + this.container.eventDispatcher.dispatchEvent(new RoomWidgetDoorbellEvent(RoomWidgetDoorbellEvent.ACCEPTED, doorbellEvent.userName)); + return; + } + } + + public processWidgetMessage(message: RoomWidgetMessage): RoomWidgetUpdateEvent + { + switch(message.type) + { + case RoomWidgetLetUserInMessage.LET_USER_IN: + const letUserInMessage = (message as RoomWidgetLetUserInMessage); + + this.container.roomSession.sendDoorbellApprovalMessage(letUserInMessage.userName, letUserInMessage.canEnter); + break; + } + + return null; + } + + public get type(): string + { + return RoomWidgetEnum.DOORBELL; + } + + public get eventTypes(): string[] + { + return [ + RoomSessionDoorbellEvent.DOORBELL, + RoomSessionDoorbellEvent.RSDE_REJECTED, + RoomSessionDoorbellEvent.RSDE_ACCEPTED + ]; + } + + public get messageTypes(): string[] + { + return [ + RoomWidgetLetUserInMessage.LET_USER_IN + ]; + } +} diff --git a/src/views/room/handlers/FurniChooserWidgetHandler.ts b/src/views/room/handlers/FurniChooserWidgetHandler.ts new file mode 100644 index 00000000..9dc3f12e --- /dev/null +++ b/src/views/room/handlers/FurniChooserWidgetHandler.ts @@ -0,0 +1,115 @@ +import { NitroEvent, RoomObjectCategory, RoomObjectVariable, RoomWidgetEnum } from '@nitrots/nitro-renderer'; +import { GetNitroInstance, GetRoomEngine, GetSessionDataManager } from '../../../api'; +import { RoomObjectItem } from '../../../events/room-widgets/choosers/RoomObjectItem'; +import { RoomWidgetChooserContentEvent } from '../../../events/room-widgets/choosers/RoomWidgetChooserContentEvent'; +import { dispatchUiEvent } from '../../../hooks'; +import { RoomWidgetUpdateEvent } from '../events'; +import { RoomWidgetMessage, RoomWidgetRequestWidgetMessage, RoomWidgetRoomObjectMessage } from '../messages'; +import { dynamicSort } from '../widgets/choosers/utils/sorting'; +import { RoomWidgetHandler } from './RoomWidgetHandler'; + +export class FurniChooserWidgetHandler extends RoomWidgetHandler +{ + public processEvent(event: NitroEvent): void + { + } + + public processWidgetMessage(message: RoomWidgetMessage): RoomWidgetUpdateEvent + { + if(!message) return null; + + switch(message.type) + { + case RoomWidgetRequestWidgetMessage.FURNI_CHOOSER: + this.processFurniChooser(); + break; + case RoomWidgetRoomObjectMessage.SELECT_OBJECT: + this.selectFurni(message); + break; + } + + return null; + } + + private selectFurni(message: RoomWidgetMessage): void + { + const event = message as RoomWidgetRoomObjectMessage; + + if(event == null) return; + + if(event.category === RoomObjectCategory.WALL || event.category === RoomObjectCategory.FLOOR) + { + GetRoomEngine().selectRoomObject(this.container.roomSession.roomId, event.id, event.category); + } + } + + private processFurniChooser(): void + { + + if(this.container == null || this.container.roomSession == null || GetRoomEngine() == null || this.container.roomSession.userDataManager == null) return; + + const roomId = this.container.roomSession.roomId; + const furniInRoom : RoomObjectItem[] = []; + + const wallItems = GetRoomEngine().getRoomObjects(roomId, RoomObjectCategory.WALL); + const floorItems = GetRoomEngine().getRoomObjects(roomId, RoomObjectCategory.FLOOR); + + wallItems.forEach( wallItem => { + if(!wallItem) return; + + const type = wallItem.type; + let name = null; + if(type.startsWith('poster')) + { + const posterNumber = Number.parseInt(type.replace('poster', '')); + name = GetNitroInstance().localization.getValue('poster_' + posterNumber + '_name'); + } + else + { + const furniTypeId = Number.parseInt(wallItem.model.getValue(RoomObjectVariable.FURNITURE_TYPE_ID)); + const wallItemData = GetSessionDataManager().getWallItemData(furniTypeId); + if(wallItemData != null && wallItemData.name.length > 0) + { + name = wallItemData.name; + } + else + { + name = type; + } + } + furniInRoom.push(new RoomObjectItem(wallItem.id, RoomObjectCategory.WALL, name)); + }); + + floorItems.forEach(roomObject => { + if(!roomObject) return; + + const furniTypeId = Number.parseInt(roomObject.model.getValue(RoomObjectVariable.FURNITURE_TYPE_ID)); + const floorItemData = GetSessionDataManager().getFloorItemData(furniTypeId); + const name = floorItemData != null ? floorItemData.name : roomObject.type; + + furniInRoom.push(new RoomObjectItem(roomObject.id, RoomObjectCategory.FLOOR, name)); + }); + + furniInRoom.sort(dynamicSort('name')); + + dispatchUiEvent(new RoomWidgetChooserContentEvent(RoomWidgetChooserContentEvent.FURNI_CHOOSER_CONTENT, furniInRoom, false)); + } + + public get type(): string + { + return RoomWidgetEnum.FURNI_CHOOSER; + } + + public get eventTypes(): string[] + { + return []; + } + + public get messageTypes(): string[] + { + return [ + RoomWidgetRequestWidgetMessage.FURNI_CHOOSER, + RoomWidgetRoomObjectMessage.SELECT_OBJECT + ]; + } +} diff --git a/src/views/room/handlers/UserChooserWidgetHandler.ts b/src/views/room/handlers/UserChooserWidgetHandler.ts new file mode 100644 index 00000000..ce889635 --- /dev/null +++ b/src/views/room/handlers/UserChooserWidgetHandler.ts @@ -0,0 +1,88 @@ +import { NitroEvent, RoomObjectCategory, RoomWidgetEnum } from '@nitrots/nitro-renderer'; +import { RoomWidgetHandler } from '.'; +import { GetRoomEngine } from '../../../api'; +import { RoomObjectItem } from '../../../events/room-widgets/choosers/RoomObjectItem'; +import { RoomWidgetChooserContentEvent } from '../../../events/room-widgets/choosers/RoomWidgetChooserContentEvent'; +import { dispatchUiEvent } from '../../../hooks'; +import { RoomWidgetUpdateEvent } from '../events'; +import { RoomWidgetMessage, RoomWidgetRequestWidgetMessage, RoomWidgetRoomObjectMessage } from '../messages'; +import { dynamicSort } from '../widgets/choosers/utils/sorting'; + +export class UserChooserWidgetHandler extends RoomWidgetHandler +{ + public processEvent(event: NitroEvent): void + { + } + + public processWidgetMessage(message: RoomWidgetMessage): RoomWidgetUpdateEvent + { + if(!message) return null; + + switch(message.type) + { + case RoomWidgetRequestWidgetMessage.USER_CHOOSER: + this.processUserChooser(); + break; + case RoomWidgetRoomObjectMessage.SELECT_OBJECT: + this.selectUnit(message); + break; + } + + return null; + } + + private processUserChooser(): void + { + + if(this.container == null || this.container.roomSession == null || GetRoomEngine() == null || this.container.roomSession.userDataManager == null) return; + + const roomId = this.container.roomSession.roomId; + const categoryId = RoomObjectCategory.UNIT; + const units : RoomObjectItem[] = []; + + const roomObjects = GetRoomEngine().getRoomObjects(roomId, categoryId); + + roomObjects.forEach(roomObject => { + if(!roomObject) return; + + const unitData = this.container.roomSession.userDataManager.getUserDataByIndex(roomObject.id); + + if(!unitData) return; + + units.push(new RoomObjectItem(unitData.roomIndex, categoryId, unitData.name)); + }); + + units.sort(dynamicSort('name')); + dispatchUiEvent(new RoomWidgetChooserContentEvent(RoomWidgetChooserContentEvent.USER_CHOOSER_CONTENT, units)); + } + + private selectUnit(k: RoomWidgetMessage): void + { + const event = k as RoomWidgetRoomObjectMessage; + + if(event == null) return; + + if(event.category === RoomObjectCategory.UNIT) + { + GetRoomEngine().selectRoomObject(this.container.roomSession.roomId, event.id, event.category); + } + } + + public get type(): string + { + return RoomWidgetEnum.USER_CHOOSER; + } + + public get eventTypes(): string[] + { + return []; + } + + public get messageTypes(): string[] + { + return [ + RoomWidgetRequestWidgetMessage.USER_CHOOSER, + RoomWidgetRoomObjectMessage.SELECT_OBJECT + ]; + } +} diff --git a/src/views/room/messages/RoomWidgetLetUserInMessage.ts b/src/views/room/messages/RoomWidgetLetUserInMessage.ts new file mode 100644 index 00000000..9001fe2a --- /dev/null +++ b/src/views/room/messages/RoomWidgetLetUserInMessage.ts @@ -0,0 +1,27 @@ +import { RoomWidgetMessage } from './RoomWidgetMessage'; + +export class RoomWidgetLetUserInMessage extends RoomWidgetMessage +{ + public static LET_USER_IN: string = 'RWLUIM_LET_USER_IN'; + + private _userName: string; + private _canEnter: boolean; + + constructor(userName: string, canEnter: boolean) + { + super(RoomWidgetLetUserInMessage.LET_USER_IN); + + this._userName = userName; + this._canEnter = canEnter; + } + + public get userName(): string + { + return this._userName; + } + + public get canEnter(): boolean + { + return this._canEnter; + } +} diff --git a/src/views/room/messages/index.ts b/src/views/room/messages/index.ts index 873903a7..732e5665 100644 --- a/src/views/room/messages/index.ts +++ b/src/views/room/messages/index.ts @@ -7,6 +7,7 @@ export * from './RoomWidgetChatTypingMessage'; export * from './RoomWidgetDanceMessage'; export * from './RoomWidgetFurniActionMessage'; export * from './RoomWidgetFurniToWidgetMessage'; +export * from './RoomWidgetLetUserInMessage'; export * from './RoomWidgetMessage'; export * from './RoomWidgetRequestWidgetMessage'; export * from './RoomWidgetRoomObjectMessage'; diff --git a/src/views/room/widgets/RoomWidgets.scss b/src/views/room/widgets/RoomWidgets.scss index ed284cd9..c621d250 100644 --- a/src/views/room/widgets/RoomWidgets.scss +++ b/src/views/room/widgets/RoomWidgets.scss @@ -3,7 +3,9 @@ @import './chat/ChatWidgetView'; @import './chat-input/ChatInputView'; @import './context-menu/ContextMenu'; +@import './doorbell/DoorbellWidgetView'; @import './furniture/FurnitureWidgets'; @import './infostand/InfoStandWidgetView'; @import './object-location/ObjectLocationView'; @import './room-tools/RoomToolsWidgetView'; +@import './choosers/ChooserWidgetView'; diff --git a/src/views/room/widgets/RoomWidgetsView.tsx b/src/views/room/widgets/RoomWidgetsView.tsx index 2ed8ba9d..1508aa6e 100644 --- a/src/views/room/widgets/RoomWidgetsView.tsx +++ b/src/views/room/widgets/RoomWidgetsView.tsx @@ -9,6 +9,9 @@ import { AvatarInfoWidgetView } from './avatar-info/AvatarInfoWidgetView'; import { CameraWidgetView } from './camera/CameraWidgetView'; import { ChatInputView } from './chat-input/ChatInputView'; import { ChatWidgetView } from './chat/ChatWidgetView'; +import { FurniChooserWidgetView } from './choosers/FurniChooserWidgetView'; +import { UserChooserWidgetView } from './choosers/UserChooserWidgetView'; +import { DoorbellWidgetView } from './doorbell/DoorbellWidgetView'; import { FurnitureWidgetsView } from './furniture/FurnitureWidgetsView'; import { InfoStandWidgetView } from './infostand/InfoStandWidgetView'; import { RoomThumbnailWidgetView } from './room-thumbnail/RoomThumbnailWidgetView'; @@ -242,10 +245,13 @@ export const RoomWidgetsView: FC = props => + + + ); } diff --git a/src/views/room/widgets/choosers/ChooserWidgetView.scss b/src/views/room/widgets/choosers/ChooserWidgetView.scss new file mode 100644 index 00000000..bca2d777 --- /dev/null +++ b/src/views/room/widgets/choosers/ChooserWidgetView.scss @@ -0,0 +1,9 @@ +.chooser-widget { + .selected-item { + background-color: cadetblue; + } + .list-item { + color: black; + overflow: hidden; + } +} diff --git a/src/views/room/widgets/choosers/ChooserWidgetView.tsx b/src/views/room/widgets/choosers/ChooserWidgetView.tsx new file mode 100644 index 00000000..05d3e7bf --- /dev/null +++ b/src/views/room/widgets/choosers/ChooserWidgetView.tsx @@ -0,0 +1,87 @@ +import { FC, useCallback, useEffect, useState } from 'react'; +import List from 'react-virtualized/dist/commonjs/List'; +import { RoomObjectItem } from '../../../../events/room-widgets/choosers/RoomObjectItem'; +import { CreateEventDispatcherHook } from '../../../../hooks'; +import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../layout'; +import { LocalizeText } from '../../../../utils'; +import { useRoomContext } from '../../context/RoomContext'; +import { RoomWidgetRoomObjectUpdateEvent } from '../../events'; +import { RoomWidgetRequestWidgetMessage, RoomWidgetRoomObjectMessage } from '../../messages'; +import { ChooserWidgetViewProps } from './ChooserWidgetView.type'; + +export const ChooserWidgetView: FC = props => +{ + const [filteredItems, setFilteredItems] = useState([]); + const [selectedItem, setSelectedItem] = useState(null); + const [refreshTimeout, setRefreshTimeout] = useState>(null); + const [searchValue, setSearchValue] = useState(''); + const { title = null, onCloseClick = null, displayItemId = false, items = null, messageType = null, roomWidgetRoomObjectUpdateEvents = null } = props; + const { eventDispatcher = null, widgetHandler = null } = useRoomContext(); + + useEffect(() => + { + if (!items) return; + + const filteredGroupItems = items.filter(item => + { + return item.name.toLocaleLowerCase().includes(searchValue.toLocaleLowerCase()); + }); + + setFilteredItems(filteredGroupItems); + }, [items, searchValue, setFilteredItems]); + + const onRoomWidgetRoomObjectUpdateEvent = useCallback((event: RoomWidgetRoomObjectUpdateEvent) => + { + if (!event) return; + + if (refreshTimeout) clearTimeout(refreshTimeout); + + setRefreshTimeout(setTimeout(() => + { + widgetHandler.processWidgetMessage(new RoomWidgetRequestWidgetMessage(messageType)); + }, 100)); + + }, [refreshTimeout, messageType, widgetHandler]); + + roomWidgetRoomObjectUpdateEvents.forEach(event => CreateEventDispatcherHook(event, eventDispatcher, onRoomWidgetRoomObjectUpdateEvent)); + + const onClickItem = useCallback((item: RoomObjectItem) => + { + setSelectedItem(item); + widgetHandler.processWidgetMessage(new RoomWidgetRoomObjectMessage(RoomWidgetRoomObjectMessage.SELECT_OBJECT, item.id, item.category)); + }, [setSelectedItem, widgetHandler]); + + const rowRenderer = function ({ + key, // Unique key within array of rows + index, // Index of row within collection + isScrolling, // The List is currently being scrolled + isVisible, // This row is visible within the List (eg it is not an overscanned row) + style, // Style object to be applied to row (to position it) + }) + { + return ( +
onClickItem(filteredItems[index])} className={(selectedItem === filteredItems[index] ? 'selected-item ' : '') + 'list-item'}> + {filteredItems[index].name} + {displayItemId && (' - ' + filteredItems[index].id)} +
+ ); + } + + return ( + + + +
+
+ setSearchValue(event.target.value)} /> +
+
+ +
+
+ ); +} diff --git a/src/views/room/widgets/choosers/ChooserWidgetView.type.ts b/src/views/room/widgets/choosers/ChooserWidgetView.type.ts new file mode 100644 index 00000000..87c9ef9f --- /dev/null +++ b/src/views/room/widgets/choosers/ChooserWidgetView.type.ts @@ -0,0 +1,12 @@ +import { MouseEvent } from 'react'; +import { RoomObjectItem } from '../../../../events/room-widgets/choosers/RoomObjectItem'; + +export interface ChooserWidgetViewProps +{ + title: string; + onCloseClick: (event: MouseEvent) => void; + displayItemId: boolean; + items: RoomObjectItem[]; + messageType: string; + roomWidgetRoomObjectUpdateEvents: string[]; +} diff --git a/src/views/room/widgets/choosers/FurniChooserWidgetView.tsx b/src/views/room/widgets/choosers/FurniChooserWidgetView.tsx new file mode 100644 index 00000000..d3d1b617 --- /dev/null +++ b/src/views/room/widgets/choosers/FurniChooserWidgetView.tsx @@ -0,0 +1,37 @@ +import { FC, useCallback, useState } from 'react'; +import { RoomObjectItem } from '../../../../events/room-widgets/choosers/RoomObjectItem'; +import { RoomWidgetChooserContentEvent } from '../../../../events/room-widgets/choosers/RoomWidgetChooserContentEvent'; +import { useUiEvent } from '../../../../hooks'; +import { LocalizeText } from '../../../../utils'; +import { RoomWidgetRoomObjectUpdateEvent } from '../../events'; +import { RoomWidgetRequestWidgetMessage } from '../../messages'; +import { ChooserWidgetView } from './ChooserWidgetView'; + +export const FurniChooserWidgetView: FC = props => +{ + const [isVisible, setIsVisible] = useState(false); + const [items, setItems] = useState(null); + + const onFurniChooserContent = useCallback((event: RoomWidgetChooserContentEvent) => + { + setItems(event.items); + setIsVisible(true); + }, []); + + useUiEvent(RoomWidgetChooserContentEvent.FURNI_CHOOSER_CONTENT, onFurniChooserContent); + + + const onClose = useCallback(() => + { + setIsVisible(false); + setItems(null); + }, []); + + if (!isVisible) return null; + + return ( +
+ +
+ ) +} diff --git a/src/views/room/widgets/choosers/UserChooserWidgetView.tsx b/src/views/room/widgets/choosers/UserChooserWidgetView.tsx new file mode 100644 index 00000000..e7f1e3be --- /dev/null +++ b/src/views/room/widgets/choosers/UserChooserWidgetView.tsx @@ -0,0 +1,36 @@ +import { FC, useCallback, useState } from 'react'; +import { RoomObjectItem } from '../../../../events/room-widgets/choosers/RoomObjectItem'; +import { RoomWidgetChooserContentEvent } from '../../../../events/room-widgets/choosers/RoomWidgetChooserContentEvent'; +import { useUiEvent } from '../../../../hooks'; +import { LocalizeText } from '../../../../utils'; +import { RoomWidgetRoomObjectUpdateEvent } from '../../events'; +import { RoomWidgetRequestWidgetMessage } from '../../messages'; +import { ChooserWidgetView } from './ChooserWidgetView'; + +export const UserChooserWidgetView : FC = props => +{ + const [isVisible, setIsVisible] = useState(false); + const [items, setItems] = useState(null); + + const onUserChooserContent = useCallback((event: RoomWidgetChooserContentEvent) => + { + setItems(event.items); + setIsVisible(true); + }, []); + + useUiEvent(RoomWidgetChooserContentEvent.USER_CHOOSER_CONTENT, onUserChooserContent); + + const onClose = useCallback(() => + { + setIsVisible(false); + setItems(null); + }, []); + + if(!isVisible) return null; + + return ( +
+ +
+ ); +} diff --git a/src/views/room/widgets/choosers/utils/sorting.ts b/src/views/room/widgets/choosers/utils/sorting.ts new file mode 100644 index 00000000..eee12529 --- /dev/null +++ b/src/views/room/widgets/choosers/utils/sorting.ts @@ -0,0 +1,18 @@ +export function dynamicSort(property) +{ + // Source: https://stackoverflow.com/questions/1129216/sort-array-of-objects-by-string-property-value + let sortOrder = 1; + if(property[0] === '-') + { + sortOrder = -1; + property = property.substr(1); + } + return function (a,b) + { + /* next line works with strings and numbers, + * and you may want to customize it to your needs + */ + const result = (a[property] < b[property]) ? -1 : (a[property] > b[property]) ? 1 : 0; + return result * sortOrder; + }; +} diff --git a/src/views/room/widgets/doorbell/DoorbellWidgetView.scss b/src/views/room/widgets/doorbell/DoorbellWidgetView.scss new file mode 100644 index 00000000..125a5e69 --- /dev/null +++ b/src/views/room/widgets/doorbell/DoorbellWidgetView.scss @@ -0,0 +1,22 @@ +.nitro-widget-doorbell { + width: 250px; + + .content-area { + min-height: 143px; + height: 143px; + } + + .doorbell-user-list { + + .list-item { + background: $grid-active-bg-color; + } + + .col:nth-child(even) { + + .list-item { + background: $white !important; + } + } + } +} diff --git a/src/views/room/widgets/doorbell/DoorbellWidgetView.tsx b/src/views/room/widgets/doorbell/DoorbellWidgetView.tsx new file mode 100644 index 00000000..7dad34dd --- /dev/null +++ b/src/views/room/widgets/doorbell/DoorbellWidgetView.tsx @@ -0,0 +1,88 @@ +import { FC, useCallback, useState } from 'react'; +import { CreateEventDispatcherHook } from '../../../../hooks'; +import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../layout'; +import { LocalizeText } from '../../../../utils'; +import { useRoomContext } from '../../context/RoomContext'; +import { RoomWidgetDoorbellEvent } from '../../events'; +import { RoomWidgetLetUserInMessage } from '../../messages'; + +export const DoorbellWidgetView: FC<{}> = props => +{ + const [ isVisible, setIsVisible ] = useState(false); + const [ users, setUsers ] = useState([]); + const { eventDispatcher = null, widgetHandler = null } = useRoomContext(); + + const addUser = useCallback((userName: string) => + { + if(users.indexOf(userName) >= 0) return; + + const newUsers = [ ...users, userName ]; + + setUsers(newUsers); + setIsVisible(true); + }, [ users ]); + + const removeUser = useCallback((userName: string) => + { + const index = users.indexOf(userName); + + if(index === -1) return; + + const newUsers = [ ...users ]; + + newUsers.splice(index, 1); + + setUsers(newUsers); + + if(!newUsers.length) setIsVisible(false); + }, [ users ]); + + const onRoomWidgetDoorbellEvent = useCallback((event: RoomWidgetDoorbellEvent) => + { + switch(event.type) + { + case RoomWidgetDoorbellEvent.RINGING: + addUser(event.userName); + return; + case RoomWidgetDoorbellEvent.REJECTED: + case RoomWidgetDoorbellEvent.ACCEPTED: + removeUser(event.userName); + return; + } + }, [ addUser, removeUser ]); + + CreateEventDispatcherHook(RoomWidgetDoorbellEvent.RINGING, eventDispatcher, onRoomWidgetDoorbellEvent); + CreateEventDispatcherHook(RoomWidgetDoorbellEvent.REJECTED, eventDispatcher, onRoomWidgetDoorbellEvent); + CreateEventDispatcherHook(RoomWidgetDoorbellEvent.ACCEPTED, eventDispatcher, onRoomWidgetDoorbellEvent); + + const answer = useCallback((userName: string, flag: boolean) => + { + widgetHandler.processWidgetMessage(new RoomWidgetLetUserInMessage(userName, flag)); + + removeUser(userName); + }, [ widgetHandler, removeUser ]); + + if(!users.length) return null; + + return ( + + setIsVisible(false) } /> + +
+ { (users.length > 0) && users.map(userName => + { + return ( +
+ { userName } +
+ + +
+
+ ); + }) } +
+
+
+ ); +} diff --git a/src/views/toolbar/ToolbarView.tsx b/src/views/toolbar/ToolbarView.tsx index 0fcd77a0..7445958f 100644 --- a/src/views/toolbar/ToolbarView.tsx +++ b/src/views/toolbar/ToolbarView.tsx @@ -1,12 +1,12 @@ -import { DesktopViewComposer, Dispose, DropBounce, EaseOut, JumpBy, Motions, NitroToolbarAnimateIconEvent, Queue, UserFigureEvent, UserInfoDataParser, UserInfoEvent, Wait } from '@nitrots/nitro-renderer'; +import { Dispose, DropBounce, EaseOut, JumpBy, Motions, NitroToolbarAnimateIconEvent, Queue, UserFigureEvent, UserInfoDataParser, UserInfoEvent, Wait } from '@nitrots/nitro-renderer'; import { FC, useCallback, useState } from 'react'; -import { GetRoomSession, GetRoomSessionManager } from '../../api'; +import { GetRoomSession, GetRoomSessionManager, GoToDesktop } from '../../api'; import { AvatarEditorEvent, CatalogEvent, FriendListEvent, InventoryEvent, NavigatorEvent, RoomWidgetCameraEvent } from '../../events'; import { AchievementsUIEvent } from '../../events/achievements'; import { UnseenItemTrackerUpdateEvent } from '../../events/inventory/UnseenItemTrackerUpdateEvent'; import { ModToolsEvent } from '../../events/mod-tools/ModToolsEvent'; import { dispatchUiEvent, useRoomEngineEvent, useUiEvent } from '../../hooks'; -import { CreateMessageHook, SendMessageHook } from '../../hooks/messages/message-event'; +import { CreateMessageHook } from '../../hooks/messages/message-event'; import { TransitionAnimation } from '../../layout/transitions/TransitionAnimation'; import { TransitionAnimationTypes } from '../../layout/transitions/TransitionAnimation.types'; import { AvatarImageView } from '../shared/avatar-image/AvatarImageView'; @@ -128,8 +128,8 @@ export const ToolbarView: FC = props => const visitDesktop = useCallback(() => { if(!GetRoomSession()) return; - - SendMessageHook(new DesktopViewComposer()); + + GoToDesktop(); GetRoomSessionManager().removeSession(-1); }, []);