mirror of
https://github.com/duckietm/Nitro-V3.git
synced 2026-06-19 15:06:20 +00:00
wired-tools: hoist inspection selection (+ live state + action version) to the store
Five more useStates leave WiredCreatorToolsView: selectedFurni, selectedFurniLiveState, selectedUser, selectedUserLiveState, and the monotonic selectedUserActionVersion counter. All five now live in useWiredCreatorToolsUiStore; the room-event listeners (useObjectSelectedEvent, the per-kind useMessageEvent + useNitroEvent handlers, the per-action effects that bump the version counter) stay in the component because they need React's subscription lifecycle — they just call the store actions instead of setState. Same persistence benefit as the previous monitorSnapshot pass: the currently-inspected target survives a panel close/reopen instead of being dropped to null on remount. Live-state setters and the action version counter accept Updater<T> so the many `previousValue => ...` call sites stayed verbatim. Tests: six new cases (setSelectedFurni + null clear, functional updater on FurniLiveState, paired setSelectedUser + LiveState, monotonic ActionVersion via updater, close/reopen persistence). The test fixtures use the real interface shapes — InspectionFurniSelection includes a renderer-typed `info: AvatarInfoFurni` that is cast through `as never` so the test doesn't have to construct the full avatar info shape. 187/187 passing.
This commit is contained in:
@@ -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<InspectionFurniSelection>(null);
|
||||
const [ selectedFurniLiveState, setSelectedFurniLiveState ] = useState<InspectionFurniLiveState>(null);
|
||||
const [ selectedUser, setSelectedUser ] = useState<InspectionUserSelection>(null);
|
||||
const [ selectedUserLiveState, setSelectedUserLiveState ] = useState<InspectionUserLiveState>(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);
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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> = 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<boolean>) => 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<InspectionFurniLiveState | null>) => void;
|
||||
setSelectedUser: (next: InspectionUserSelection | null) => void;
|
||||
setSelectedUserLiveState: (next: Updater<InspectionUserLiveState | null>) => void;
|
||||
setSelectedUserActionVersion: (next: Updater<number>) => void;
|
||||
}
|
||||
|
||||
export const useWiredCreatorToolsUiStore = createNitroStore<WiredCreatorToolsUiState>()((set) => ({
|
||||
@@ -80,6 +104,12 @@ export const useWiredCreatorToolsUiStore = createNitroStore<WiredCreatorToolsUiS
|
||||
|
||||
monitorSnapshot: createEmptyMonitorSnapshot(),
|
||||
|
||||
selectedFurni: null,
|
||||
selectedFurniLiveState: null,
|
||||
selectedUser: null,
|
||||
selectedUserLiveState: null,
|
||||
selectedUserActionVersion: 0,
|
||||
|
||||
setIsVisible: (next) => 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<WiredCreatorToolsUiS
|
||||
setVariableManagePage: (next) => 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) }))
|
||||
}));
|
||||
|
||||
Reference in New Issue
Block a user