diff --git a/src/hooks/rooms/widgets/index.ts b/src/hooks/rooms/widgets/index.ts index 2906c74..65602bf 100644 --- a/src/hooks/rooms/widgets/index.ts +++ b/src/hooks/rooms/widgets/index.ts @@ -15,5 +15,7 @@ export * from './usePetPackageWidget'; export * from './usePollActions'; export * from './usePollSubscriptions'; export * from './usePollWidget'; +export * from './useUserChooserActions'; +export * from './useUserChooserState'; export * from './useUserChooserWidget'; export * from './useWordQuizWidget'; diff --git a/src/hooks/rooms/widgets/useUserChooserActions.ts b/src/hooks/rooms/widgets/useUserChooserActions.ts new file mode 100644 index 0000000..073564a --- /dev/null +++ b/src/hooks/rooms/widgets/useUserChooserActions.ts @@ -0,0 +1,16 @@ +import { GetRoomEngine } from '@nitrots/nitro-renderer'; +import { GetRoomSession, RoomObjectItem } from '../../../api'; + +/** + * Imperative actions for the User chooser. Stateless — split from + * useUserChooserState so components that only need to dispatch a + * selection don't subscribe to the user-add/remove lifecycle events. + */ +export const useUserChooserActions = () => ({ + selectItem: (item: RoomObjectItem): void => + { + if(!item) return; + + GetRoomEngine().selectRoomObject(GetRoomSession().roomId, item.id, item.category); + } +}); diff --git a/src/hooks/rooms/widgets/useUserChooserState.ts b/src/hooks/rooms/widgets/useUserChooserState.ts new file mode 100644 index 0000000..3a41c19 --- /dev/null +++ b/src/hooks/rooms/widgets/useUserChooserState.ts @@ -0,0 +1,92 @@ +import { GetRoomEngine, RoomObjectCategory } from '@nitrots/nitro-renderer'; +import { useState } from 'react'; +import { GetRoomSession, RoomObjectItem } from '../../../api'; +import { useUserAddedEvent, useUserRemovedEvent } from '../engine'; + +const resolveUserType = (userType: number): string => +{ + switch(userType) + { + case 1: return 'Habbo'; + case 2: return 'Pet'; + case 3: return 'Bot'; + default: return '-'; + } +}; + +const buildUserItem = (roomIndex: number): RoomObjectItem | null => +{ + if(roomIndex < 0) return null; + + const userData = GetRoomSession()?.userDataManager?.getUserDataByIndex(roomIndex); + + if(!userData) return null; + if(userData.type !== 1) return null; + + return new RoomObjectItem(userData.roomIndex, RoomObjectCategory.UNIT, userData.name, 0, '-', resolveUserType(userData.type)); +}; + +/** + * State + event subscriptions for the User chooser widget. Pure + * imperative actions (selectItem) live in useUserChooserActions. + */ +export const useUserChooserState = () => +{ + const [ items, setItems ] = useState(null); + + const onClose = () => setItems(null); + + const populateChooser = () => + { + const session = GetRoomSession(); + + if(!session) return; + + const roomObjects = GetRoomEngine().getRoomObjects(session.roomId, RoomObjectCategory.UNIT); + + setItems(roomObjects + .map(roomObject => buildUserItem(roomObject.id)) + .filter((item): item is RoomObjectItem => item !== null) + .sort((a, b) => ((a.name < b.name) ? -1 : 1))); + }; + + useUserAddedEvent(!!items, event => + { + const item = buildUserItem(event.id); + + if(!item) return; + + setItems(prevValue => + { + const newValue = [ ...(prevValue ?? []), item ]; + newValue.sort((a, b) => ((a.name < b.name) ? -1 : 1)); + return newValue; + }); + }); + + useUserRemovedEvent(!!items, event => + { + if(event.id < 0) return; + + setItems(prevValue => + { + if(!prevValue) return prevValue; + + const newValue = [ ...prevValue ]; + + for(let i = 0; i < newValue.length; i++) + { + const existingValue = newValue[i]; + + if((existingValue.id !== event.id) || (existingValue.category !== event.category)) continue; + + newValue.splice(i, 1); + break; + } + + return newValue; + }); + }); + + return { items, onClose, populateChooser }; +}; diff --git a/src/hooks/rooms/widgets/useUserChooserWidget.ts b/src/hooks/rooms/widgets/useUserChooserWidget.ts index 7cbac46..1d3e7df 100644 --- a/src/hooks/rooms/widgets/useUserChooserWidget.ts +++ b/src/hooks/rooms/widgets/useUserChooserWidget.ts @@ -1,97 +1,16 @@ -import { GetRoomEngine, RoomObjectCategory } from '@nitrots/nitro-renderer'; -import { useState } from 'react'; -import { GetRoomSession, RoomObjectItem } from '../../../api'; -import { useUserAddedEvent, useUserRemovedEvent } from '../engine'; -import { useRoom } from '../useRoom'; +import { useUserChooserActions } from './useUserChooserActions'; +import { useUserChooserState } from './useUserChooserState'; -const resolveUserType = (userType: number): string => +/** + * @deprecated Use `useUserChooserState` (data + close + populate) and + * `useUserChooserActions` (imperative selectItem) directly. This shim + * preserves the `{ items, onClose, selectItem, populateChooser }` shape + * for existing consumers. + */ +export const useUserChooserWidget = () => { - switch(userType) - { - case 1: return 'Habbo'; - case 2: return 'Pet'; - case 3: return 'Bot'; - default: return '-'; - } -}; - -const useUserChooserWidgetState = () => -{ - const [ items, setItems ] = useState(null); - const { roomSession = null } = useRoom(); - - const onClose = () => setItems(null); - - const selectItem = (item: RoomObjectItem) => item && GetRoomEngine().selectRoomObject(GetRoomSession().roomId, item.id, item.category); - - const populateChooser = () => - { - const roomSession = GetRoomSession(); - const roomObjects = GetRoomEngine().getRoomObjects(roomSession.roomId, RoomObjectCategory.UNIT); - - setItems(roomObjects - .map(roomObject => - { - if(roomObject.id < 0) return null; - - const userData = roomSession.userDataManager.getUserDataByIndex(roomObject.id); - - if(!userData) return null; - if(userData.type !== 1) return null; - - const type = resolveUserType(userData.type); - return new RoomObjectItem(userData.roomIndex, RoomObjectCategory.UNIT, userData.name, 0, '-', type); - }) - .filter(Boolean) - .sort((a, b) => ((a.name < b.name) ? -1 : 1))); - }; - - useUserAddedEvent(!!items, event => - { - if(event.id < 0) return; - - const userData = GetRoomSession().userDataManager.getUserDataByIndex(event.id); - - if(!userData) return; - if(userData.type !== 1) return; - - const type = resolveUserType(userData.type); - - setItems(prevValue => - { - const newValue = [ ...prevValue ]; - - newValue.push(new RoomObjectItem(userData.roomIndex, RoomObjectCategory.UNIT, userData.name, 0, '-', type)); - newValue.sort((a, b) => ((a.name < b.name) ? -1 : 1)); - - return newValue; - }); - }); - - useUserRemovedEvent(!!items, event => - { - if(event.id < 0) return; - - setItems(prevValue => - { - const newValue = [ ...prevValue ]; - - for(let i = 0; i < newValue.length; i++) - { - const existingValue = newValue[i]; - - if((existingValue.id !== event.id) || (existingValue.category !== event.category)) continue; - - newValue.splice(i, 1); - - break; - } - - return newValue; - }); - }); + const { items, onClose, populateChooser } = useUserChooserState(); + const { selectItem } = useUserChooserActions(); return { items, onClose, selectItem, populateChooser }; }; - -export const useUserChooserWidget = useUserChooserWidgetState;