diff --git a/src/components/wired-tools/WiredCreatorToolsView.tsx b/src/components/wired-tools/WiredCreatorToolsView.tsx index 2018c16..5ada188 100644 --- a/src/components/wired-tools/WiredCreatorToolsView.tsx +++ b/src/components/wired-tools/WiredCreatorToolsView.tsx @@ -25,11 +25,16 @@ export const WiredCreatorToolsView: FC<{}> = () => const setInspectionType = useWiredCreatorToolsUiStore(s => s.setInspectionType); const variablesType = useWiredCreatorToolsUiStore(s => s.variablesType); const [ keepSelected, setKeepSelected ] = useState(false); - const [ selectedFurni, setSelectedFurni ] = useState(null); - const [ selectedFurniLiveState, setSelectedFurniLiveState ] = useState(null); - const [ selectedUser, setSelectedUser ] = useState(null); - const [ selectedUserLiveState, setSelectedUserLiveState ] = useState(null); - const [ selectedUserActionVersion, setSelectedUserActionVersion ] = useState(0); + const selectedFurni = useWiredCreatorToolsUiStore(s => s.selectedFurni); + const setSelectedFurni = useWiredCreatorToolsUiStore(s => s.setSelectedFurni); + const selectedFurniLiveState = useWiredCreatorToolsUiStore(s => s.selectedFurniLiveState); + const setSelectedFurniLiveState = useWiredCreatorToolsUiStore(s => s.setSelectedFurniLiveState); + const selectedUser = useWiredCreatorToolsUiStore(s => s.selectedUser); + const setSelectedUser = useWiredCreatorToolsUiStore(s => s.setSelectedUser); + const selectedUserLiveState = useWiredCreatorToolsUiStore(s => s.selectedUserLiveState); + const setSelectedUserLiveState = useWiredCreatorToolsUiStore(s => s.setSelectedUserLiveState); + const selectedUserActionVersion = useWiredCreatorToolsUiStore(s => s.selectedUserActionVersion); + const setSelectedUserActionVersion = useWiredCreatorToolsUiStore(s => s.setSelectedUserActionVersion); const [ globalClock, setGlobalClock ] = useState(Date.now()); const [ roomEnteredAt, setRoomEnteredAt ] = useState(Date.now()); const monitorSnapshot = useWiredCreatorToolsUiStore(s => s.monitorSnapshot); diff --git a/src/components/wired-tools/wiredCreatorToolsUiStore.test.ts b/src/components/wired-tools/wiredCreatorToolsUiStore.test.ts index 6c5f54e..b1ce3f5 100644 --- a/src/components/wired-tools/wiredCreatorToolsUiStore.test.ts +++ b/src/components/wired-tools/wiredCreatorToolsUiStore.test.ts @@ -17,7 +17,12 @@ const INITIAL = { variableManageTypeFilter: 'ALL', variableManageSort: 'highest_value', variableManagePage: 1, - monitorSnapshot: createEmptyMonitorSnapshot() + monitorSnapshot: createEmptyMonitorSnapshot(), + selectedFurni: null, + selectedFurniLiveState: null, + selectedUser: null, + selectedUserLiveState: null, + selectedUserActionVersion: 0 }; describe('useWiredCreatorToolsUiStore', () => @@ -46,6 +51,11 @@ describe('useWiredCreatorToolsUiStore', () => expect(state.variableManageSort).toBe('highest_value'); expect(state.variableManagePage).toBe(1); expect(state.monitorSnapshot).toEqual(createEmptyMonitorSnapshot()); + expect(state.selectedFurni).toBeNull(); + expect(state.selectedFurniLiveState).toBeNull(); + expect(state.selectedUser).toBeNull(); + expect(state.selectedUserLiveState).toBeNull(); + expect(state.selectedUserActionVersion).toBe(0); }); describe('setIsVisible', () => @@ -233,4 +243,75 @@ describe('useWiredCreatorToolsUiStore', () => expect(useWiredCreatorToolsUiStore.getState().monitorSnapshot.usageCurrentWindow).toBe(3); }); }); + + describe('inspection selection', () => + { + const furniSelection = { + objectId: 42, + category: 10, + info: { id: 42, name: 'sofa', description: '', image: null } as never + }; + const userSelection = { + kind: 'user' as const, + roomIndex: 7, + name: 'simoleo', + figure: 'hd-180-1.lg-3023-110', + gender: 'M', + userId: 99, + level: 12, + posture: 'std' + } as never; + + it('setSelectedFurni stores the picked furni selection', () => + { + useWiredCreatorToolsUiStore.getState().setSelectedFurni(furniSelection); + + expect(useWiredCreatorToolsUiStore.getState().selectedFurni).toEqual(furniSelection); + }); + + it('setSelectedFurni(null) clears the selection (deselect path)', () => + { + useWiredCreatorToolsUiStore.getState().setSelectedFurni(furniSelection); + useWiredCreatorToolsUiStore.getState().setSelectedFurni(null); + + expect(useWiredCreatorToolsUiStore.getState().selectedFurni).toBeNull(); + }); + + it('setSelectedFurniLiveState accepts a functional updater', () => + { + const initial = { positionX: 1, positionY: 2, altitude: 3, rotation: 4, state: 5 }; + useWiredCreatorToolsUiStore.getState().setSelectedFurniLiveState(initial); + + useWiredCreatorToolsUiStore.getState().setSelectedFurniLiveState(prev => (prev ? { ...prev, state: prev.state + 1 } : null)); + + expect(useWiredCreatorToolsUiStore.getState().selectedFurniLiveState).toEqual({ ...initial, state: 6 }); + }); + + it('setSelectedUser + setSelectedUserLiveState write the user selection / live state', () => + { + useWiredCreatorToolsUiStore.getState().setSelectedUser(userSelection); + useWiredCreatorToolsUiStore.getState().setSelectedUserLiveState({ positionX: 5, positionY: 6, altitude: 0, direction: 2 }); + + expect(useWiredCreatorToolsUiStore.getState().selectedUser).toEqual(userSelection); + expect(useWiredCreatorToolsUiStore.getState().selectedUserLiveState).toEqual({ positionX: 5, positionY: 6, altitude: 0, direction: 2 }); + }); + + it('setSelectedUserActionVersion bumps the monotonic counter via functional updater', () => + { + useWiredCreatorToolsUiStore.getState().setSelectedUserActionVersion(prev => prev + 1); + useWiredCreatorToolsUiStore.getState().setSelectedUserActionVersion(prev => prev + 1); + useWiredCreatorToolsUiStore.getState().setSelectedUserActionVersion(prev => prev + 1); + + expect(useWiredCreatorToolsUiStore.getState().selectedUserActionVersion).toBe(3); + }); + + it('the selection persists across the panel close/reopen lifecycle', () => + { + useWiredCreatorToolsUiStore.getState().setSelectedFurni(furniSelection); + useWiredCreatorToolsUiStore.getState().setIsVisible(false); + useWiredCreatorToolsUiStore.getState().setIsVisible(true); + + expect(useWiredCreatorToolsUiStore.getState().selectedFurni).toEqual(furniSelection); + }); + }); }); diff --git a/src/components/wired-tools/wiredCreatorToolsUiStore.ts b/src/components/wired-tools/wiredCreatorToolsUiStore.ts index f4e754c..e117eb5 100644 --- a/src/components/wired-tools/wiredCreatorToolsUiStore.ts +++ b/src/components/wired-tools/wiredCreatorToolsUiStore.ts @@ -1,6 +1,6 @@ import { createNitroStore } from '../../state/createNitroStore'; import { createEmptyMonitorSnapshot } from './WiredCreatorTools.helpers'; -import { InspectionElementType, MonitorSnapshot, VariablesElementType, WiredToolsTab } from './WiredCreatorTools.types'; +import { InspectionElementType, InspectionFurniLiveState, InspectionFurniSelection, InspectionUserLiveState, InspectionUserSelection, MonitorSnapshot, VariablesElementType, WiredToolsTab } from './WiredCreatorTools.types'; type MonitorSeverityFilter = 'ALL' | 'ERROR' | 'WARNING'; type Updater = T | ((prev: T) => T); @@ -37,6 +37,24 @@ interface WiredCreatorToolsUiState */ monitorSnapshot: MonitorSnapshot; + /** + * Inspection selection. The room-event listeners + * (`useObjectSelectedEvent` and the per-kind `useMessageEvent` + * handlers) still live in `WiredCreatorToolsView` — they need React + * lifecycle to subscribe/unsubscribe correctly — but the resulting + * state lives here so a closed/reopened panel keeps the last + * inspected target. + * + * `*ActionVersion` is a monotonic counter the user-action handlers + * bump to force the live-state recomputation effect to re-run even + * when neither `selectedUser` nor `roomIndex` changed identity. + */ + selectedFurni: InspectionFurniSelection | null; + selectedFurniLiveState: InspectionFurniLiveState | null; + selectedUser: InspectionUserSelection | null; + selectedUserLiveState: InspectionUserLiveState | null; + selectedUserActionVersion: number; + setIsVisible: (next: Updater) => void; setActiveTab: (next: WiredToolsTab) => void; setInspectionType: (next: InspectionElementType) => void; @@ -57,6 +75,12 @@ interface WiredCreatorToolsUiState setMonitorSnapshot: (next: MonitorSnapshot) => void; resetMonitorSnapshot: () => void; + + setSelectedFurni: (next: InspectionFurniSelection | null) => void; + setSelectedFurniLiveState: (next: Updater) => void; + setSelectedUser: (next: InspectionUserSelection | null) => void; + setSelectedUserLiveState: (next: Updater) => void; + setSelectedUserActionVersion: (next: Updater) => void; } export const useWiredCreatorToolsUiStore = createNitroStore()((set) => ({ @@ -80,6 +104,12 @@ export const useWiredCreatorToolsUiStore = createNitroStore set(state => ({ isVisible: apply(state.isVisible, next) })), setActiveTab: (next) => set({ activeTab: next }), setInspectionType: (next) => set({ inspectionType: next }), @@ -99,5 +129,11 @@ export const useWiredCreatorToolsUiStore = createNitroStore set(state => ({ variableManagePage: apply(state.variableManagePage, next) })), setMonitorSnapshot: (next) => set({ monitorSnapshot: next }), - resetMonitorSnapshot: () => set({ monitorSnapshot: createEmptyMonitorSnapshot() }) + resetMonitorSnapshot: () => set({ monitorSnapshot: createEmptyMonitorSnapshot() }), + + setSelectedFurni: (next) => set({ selectedFurni: next }), + setSelectedFurniLiveState: (next) => set(state => ({ selectedFurniLiveState: apply(state.selectedFurniLiveState, next) })), + setSelectedUser: (next) => set({ selectedUser: next }), + setSelectedUserLiveState: (next) => set(state => ({ selectedUserLiveState: apply(state.selectedUserLiveState, next) })), + setSelectedUserActionVersion: (next) => set(state => ({ selectedUserActionVersion: apply(state.selectedUserActionVersion, next) })) }));