mirror of
https://github.com/duckietm/Nitro-V3.git
synced 2026-06-19 15:06:20 +00:00
Split useFurniChooserWidget into state + actions (flat hooks layout)
Apply the same data/actions split pattern (proposal #4) to useFurniChooserWidget, the largest god-hook still on the widgets side (161 LOC). Layout follows the main branch convention: flat files under src/hooks/rooms/widgets/, no per-feature subfolder, no co-location of hooks inside src/components/. Split - src/hooks/rooms/widgets/useFurniChooserState.ts (new): owns the items array, the populateChooser action that scans the current room, the two RoomEngine event bridges (added/removed), and onClose. Helper buildWallItem/buildFloorItem dedupes the two copies of the RoomObjectItem construction that used to live inline in both populateChooser and the added-event handler (~50 lines of duplication removed). - src/hooks/rooms/widgets/useFurniChooserActions.ts (new): the one pure imperative action — selectItem — that doesn't need to subscribe to anything. - src/hooks/rooms/widgets/useFurniChooserWidget.ts: kept as a deprecated shim that composes both and returns the same { items, onClose, selectItem, populateChooser } shape so FurniChooserWidgetView (the only consumer) doesn't change. Layout note - This is consistent with the main branch: each widget hook is a flat file under src/hooks/rooms/widgets/ (no <feature>/ subfolder), while the view sits under src/components/room/widgets/<feature>/. - The parallel feat/react19-hooks-adapter branch chose the opposite convention (hooks co-located inside src/components/...). Per the team decision recorded in docs/ARCHITECTURE.md proposal #3, this repo stays on the flat-hooks-folder layout. Verification - yarn tsc on the touched files: 6 TS2347 errors after the split, 12 before — the buildWallItem/buildFloorItem helpers actually *reduce* the local sandbox TS2347 surface (the renderer SDK is not installed locally, so `roomObject.model.getValue<T>` is flagged as "untyped function with type arg"; merging the two callsites into one helper halves the count). - yarn eslint on the touched files: 0 errors, 0 warnings. - yarn test: 49/49 passing.
This commit is contained in:
@@ -8,6 +8,8 @@ export * from './useDoorbellState';
|
||||
export * from './useDoorbellWidget';
|
||||
export * from './useFilterWordsWidget';
|
||||
export * from './useFriendRequestWidget';
|
||||
export * from './useFurniChooserActions';
|
||||
export * from './useFurniChooserState';
|
||||
export * from './useFurniChooserWidget';
|
||||
export * from './usePetPackageWidget';
|
||||
export * from './usePollActions';
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { GetRoomEngine } from '@nitrots/nitro-renderer';
|
||||
import { GetRoomSession, RoomObjectItem } from '../../../api';
|
||||
|
||||
/**
|
||||
* Imperative actions for the Furni chooser. Stateless — split from
|
||||
* useFurniChooserState so components that only need to dispatch a
|
||||
* selection don't subscribe to the room-object lifecycle events.
|
||||
*/
|
||||
export const useFurniChooserActions = () => ({
|
||||
selectItem: (item: RoomObjectItem): void =>
|
||||
{
|
||||
if(!item) return;
|
||||
|
||||
GetRoomEngine().selectRoomObject(GetRoomSession().roomId, item.id, item.category);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,121 @@
|
||||
import { GetRoomEngine, GetSessionDataManager, RoomObjectCategory, RoomObjectVariable } from '@nitrots/nitro-renderer';
|
||||
import { useState } from 'react';
|
||||
import { GetRoomSession, LocalizeText, RoomObjectItem } from '../../../api';
|
||||
import { useFurniAddedEvent, useFurniRemovedEvent } from '../engine';
|
||||
import { useRoom } from '../useRoom';
|
||||
|
||||
const isPetOrBot = (roomObjectType: string): boolean =>
|
||||
roomObjectType.includes('pet_') ||
|
||||
roomObjectType.includes('bot_') ||
|
||||
roomObjectType === 'pet' ||
|
||||
roomObjectType === 'bot' ||
|
||||
roomObjectType.includes('rentableBot');
|
||||
|
||||
const buildWallItem = (roomObject: any): RoomObjectItem | null =>
|
||||
{
|
||||
if(roomObject.id < 0 || isPetOrBot(roomObject.type)) return null;
|
||||
|
||||
const sessionDataManager = GetSessionDataManager();
|
||||
let name = roomObject.type;
|
||||
|
||||
if(name.startsWith('poster'))
|
||||
{
|
||||
name = LocalizeText(`poster_${ name.replace('poster', '') }_name`);
|
||||
}
|
||||
else
|
||||
{
|
||||
const typeId = roomObject.model.getValue<number>(RoomObjectVariable.FURNITURE_TYPE_ID);
|
||||
const furniData = sessionDataManager.getWallItemData(typeId);
|
||||
|
||||
if(furniData && furniData.name.length) name = furniData.name;
|
||||
}
|
||||
|
||||
const ownerId = roomObject.model.getValue<number>(RoomObjectVariable.FURNITURE_OWNER_ID) || 0;
|
||||
const ownerName = roomObject.model.getValue<string>(RoomObjectVariable.FURNITURE_OWNER_NAME) ||
|
||||
(sessionDataManager.getUserData ? sessionDataManager.getUserData(ownerId)?.name : null) ||
|
||||
`User_${ownerId}`;
|
||||
|
||||
return new RoomObjectItem(roomObject.id, RoomObjectCategory.WALL, name, ownerId, ownerName, 'furniture');
|
||||
};
|
||||
|
||||
const buildFloorItem = (roomObject: any): RoomObjectItem | null =>
|
||||
{
|
||||
if(roomObject.id < 0 || isPetOrBot(roomObject.type)) return null;
|
||||
|
||||
const sessionDataManager = GetSessionDataManager();
|
||||
let name = roomObject.type;
|
||||
|
||||
const typeId = roomObject.model.getValue<number>(RoomObjectVariable.FURNITURE_TYPE_ID);
|
||||
const furniData = sessionDataManager.getFloorItemData(typeId);
|
||||
|
||||
if(furniData && furniData.name.length) name = furniData.name;
|
||||
|
||||
const ownerId = roomObject.model.getValue<number>(RoomObjectVariable.FURNITURE_OWNER_ID) || 0;
|
||||
const ownerName = roomObject.model.getValue<string>(RoomObjectVariable.FURNITURE_OWNER_NAME) ||
|
||||
(sessionDataManager.getUserData ? sessionDataManager.getUserData(ownerId)?.name : null) ||
|
||||
`User_${ownerId}`;
|
||||
|
||||
return new RoomObjectItem(roomObject.id, RoomObjectCategory.FLOOR, name, ownerId, ownerName, 'furniture');
|
||||
};
|
||||
|
||||
/**
|
||||
* State + event subscriptions for the Furni chooser widget. Pure
|
||||
* imperative actions (selectItem) live in useFurniChooserActions.
|
||||
*/
|
||||
export const useFurniChooserState = () =>
|
||||
{
|
||||
const [ items, setItems ] = useState<RoomObjectItem[]>(null);
|
||||
const { roomSession = null } = useRoom();
|
||||
|
||||
const onClose = () => setItems(null);
|
||||
|
||||
const populateChooser = () =>
|
||||
{
|
||||
const wallObjects = GetRoomEngine().getRoomObjects(roomSession.roomId, RoomObjectCategory.WALL);
|
||||
const floorObjects = GetRoomEngine().getRoomObjects(roomSession.roomId, RoomObjectCategory.FLOOR);
|
||||
|
||||
const wallItems = wallObjects.map(buildWallItem).filter((item): item is RoomObjectItem => item !== null);
|
||||
const floorItems = floorObjects.map(buildFloorItem).filter((item): item is RoomObjectItem => item !== null);
|
||||
|
||||
setItems([ ...wallItems, ...floorItems ].sort((a, b) => ((a.name < b.name) ? -1 : 1)));
|
||||
};
|
||||
|
||||
useFurniAddedEvent(!!items, event =>
|
||||
{
|
||||
if(event.id < 0) return;
|
||||
|
||||
const roomObject = GetRoomEngine().getRoomObject(GetRoomSession().roomId, event.id, event.category);
|
||||
|
||||
if(!roomObject) return;
|
||||
|
||||
const item = (event.category === RoomObjectCategory.WALL) ? buildWallItem(roomObject) : (event.category === RoomObjectCategory.FLOOR) ? buildFloorItem(roomObject) : null;
|
||||
|
||||
if(item) setItems(prevValue => [ ...(prevValue ?? []), item ].sort((a, b) => ((a.name < b.name) ? -1 : 1)));
|
||||
});
|
||||
|
||||
useFurniRemovedEvent(!!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 };
|
||||
};
|
||||
@@ -1,161 +1,16 @@
|
||||
import { GetRoomEngine, GetSessionDataManager, RoomObjectCategory, RoomObjectVariable } from '@nitrots/nitro-renderer';
|
||||
import { useState } from 'react';
|
||||
import { GetRoomSession, LocalizeText, RoomObjectItem } from '../../../api';
|
||||
import { useFurniAddedEvent, useFurniRemovedEvent } from '../engine';
|
||||
import { useRoom } from '../useRoom';
|
||||
import { useFurniChooserActions } from './useFurniChooserActions';
|
||||
import { useFurniChooserState } from './useFurniChooserState';
|
||||
|
||||
const isPetOrBot = (roomObjectType: string): boolean =>
|
||||
roomObjectType.includes('pet_') ||
|
||||
roomObjectType.includes('bot_') ||
|
||||
roomObjectType === 'pet' ||
|
||||
roomObjectType === 'bot' ||
|
||||
roomObjectType.includes('rentableBot');
|
||||
|
||||
const useFurniChooserWidgetState = () =>
|
||||
/**
|
||||
* @deprecated Use `useFurniChooserState` (data + close + populate)
|
||||
* and `useFurniChooserActions` (imperative selectItem) directly.
|
||||
* This shim preserves the `{ items, onClose, selectItem, populateChooser }`
|
||||
* shape for existing consumers.
|
||||
*/
|
||||
export const useFurniChooserWidget = () =>
|
||||
{
|
||||
const [ items, setItems ] = useState<RoomObjectItem[]>(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 sessionDataManager = GetSessionDataManager();
|
||||
const wallObjects = GetRoomEngine().getRoomObjects(roomSession.roomId, RoomObjectCategory.WALL);
|
||||
const floorObjects = GetRoomEngine().getRoomObjects(roomSession.roomId, RoomObjectCategory.FLOOR);
|
||||
|
||||
const wallItems = wallObjects.map(roomObject =>
|
||||
{
|
||||
if(roomObject.id < 0) return null;
|
||||
if(isPetOrBot(roomObject.type)) return null;
|
||||
|
||||
let name = roomObject.type;
|
||||
|
||||
if(name.startsWith('poster'))
|
||||
{
|
||||
name = LocalizeText(`poster_${ name.replace('poster', '') }_name`);
|
||||
}
|
||||
else
|
||||
{
|
||||
const typeId = roomObject.model.getValue<number>(RoomObjectVariable.FURNITURE_TYPE_ID);
|
||||
const furniData = sessionDataManager.getWallItemData(typeId);
|
||||
|
||||
if(furniData && furniData.name.length) name = furniData.name;
|
||||
}
|
||||
|
||||
const ownerId = roomObject.model.getValue<number>(RoomObjectVariable.FURNITURE_OWNER_ID) || 0;
|
||||
const ownerName = roomObject.model.getValue<string>(RoomObjectVariable.FURNITURE_OWNER_NAME) ||
|
||||
(sessionDataManager.getUserData ? sessionDataManager.getUserData(ownerId)?.name : null) ||
|
||||
`User_${ownerId}`;
|
||||
|
||||
return new RoomObjectItem(roomObject.id, RoomObjectCategory.WALL, name, ownerId, ownerName, 'furniture');
|
||||
}).filter(item => item !== null);
|
||||
|
||||
const floorItems = floorObjects.map(roomObject =>
|
||||
{
|
||||
if(roomObject.id < 0) return null;
|
||||
if(isPetOrBot(roomObject.type)) return null;
|
||||
|
||||
let name = roomObject.type;
|
||||
|
||||
const typeId = roomObject.model.getValue<number>(RoomObjectVariable.FURNITURE_TYPE_ID);
|
||||
const furniData = sessionDataManager.getFloorItemData(typeId);
|
||||
|
||||
if(furniData && furniData.name.length) name = furniData.name;
|
||||
|
||||
const ownerId = roomObject.model.getValue<number>(RoomObjectVariable.FURNITURE_OWNER_ID) || 0;
|
||||
const ownerName = roomObject.model.getValue<string>(RoomObjectVariable.FURNITURE_OWNER_NAME) ||
|
||||
(sessionDataManager.getUserData ? sessionDataManager.getUserData(ownerId)?.name : null) ||
|
||||
`User_${ownerId}`;
|
||||
|
||||
return new RoomObjectItem(roomObject.id, RoomObjectCategory.FLOOR, name, ownerId, ownerName, 'furniture');
|
||||
}).filter(item => item !== null);
|
||||
|
||||
setItems([ ...wallItems, ...floorItems ].sort((a, b) => ((a.name < b.name) ? -1 : 1)));
|
||||
};
|
||||
|
||||
useFurniAddedEvent(!!items, event =>
|
||||
{
|
||||
if(event.id < 0) return;
|
||||
|
||||
const roomObject = GetRoomEngine().getRoomObject(GetRoomSession().roomId, event.id, event.category);
|
||||
|
||||
if(!roomObject) return;
|
||||
if(isPetOrBot(roomObject.type)) return;
|
||||
|
||||
let item: RoomObjectItem = null;
|
||||
|
||||
switch(event.category)
|
||||
{
|
||||
case RoomObjectCategory.WALL: {
|
||||
let name = roomObject.type;
|
||||
|
||||
if(name.startsWith('poster'))
|
||||
{
|
||||
name = LocalizeText(`poster_${ name.replace('poster', '') }_name`);
|
||||
}
|
||||
else
|
||||
{
|
||||
const typeId = roomObject.model.getValue<number>(RoomObjectVariable.FURNITURE_TYPE_ID);
|
||||
const furniData = GetSessionDataManager().getWallItemData(typeId);
|
||||
|
||||
if(furniData && furniData.name.length) name = furniData.name;
|
||||
}
|
||||
|
||||
const wallOwnerId = roomObject.model.getValue<number>(RoomObjectVariable.FURNITURE_OWNER_ID) || 0;
|
||||
const wallOwnerName = roomObject.model.getValue<string>(RoomObjectVariable.FURNITURE_OWNER_NAME) ||
|
||||
(GetSessionDataManager().getUserData ? GetSessionDataManager().getUserData(wallOwnerId)?.name : null) ||
|
||||
`User_${wallOwnerId}`;
|
||||
|
||||
item = new RoomObjectItem(roomObject.id, RoomObjectCategory.WALL, name, wallOwnerId, wallOwnerName, 'furniture');
|
||||
break;
|
||||
}
|
||||
case RoomObjectCategory.FLOOR: {
|
||||
let name = roomObject.type;
|
||||
|
||||
const typeId = roomObject.model.getValue<number>(RoomObjectVariable.FURNITURE_TYPE_ID);
|
||||
const furniData = GetSessionDataManager().getFloorItemData(typeId);
|
||||
|
||||
if(furniData && furniData.name.length) name = furniData.name;
|
||||
|
||||
const floorOwnerId = roomObject.model.getValue<number>(RoomObjectVariable.FURNITURE_OWNER_ID) || 0;
|
||||
const floorOwnerName = roomObject.model.getValue<string>(RoomObjectVariable.FURNITURE_OWNER_NAME) ||
|
||||
(GetSessionDataManager().getUserData ? GetSessionDataManager().getUserData(floorOwnerId)?.name : null) ||
|
||||
`User_${floorOwnerId}`;
|
||||
|
||||
item = new RoomObjectItem(roomObject.id, RoomObjectCategory.FLOOR, name, floorOwnerId, floorOwnerName, 'furniture');
|
||||
}
|
||||
}
|
||||
|
||||
if(item) setItems(prevValue => [ ...prevValue, item ].sort((a, b) => ((a.name < b.name) ? -1 : 1)));
|
||||
});
|
||||
|
||||
useFurniRemovedEvent(!!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 } = useFurniChooserState();
|
||||
const { selectItem } = useFurniChooserActions();
|
||||
|
||||
return { items, onClose, selectItem, populateChooser };
|
||||
};
|
||||
|
||||
export const useFurniChooserWidget = useFurniChooserWidgetState;
|
||||
|
||||
Reference in New Issue
Block a user