wired-tools: hoist variable-highlight toggle + overlays to the store

Move the highlight feature pair into useWiredCreatorToolsUiStore:
isVariableHighlightActive (toggle UI flag) and variableHighlightOverlays
(computed screen-space overlay positions). The two screen-coords effects
in WiredCreatorToolsView stay where they are (they need React's
lifecycle to install / tear down WiredSelectionVisualizer highlights on
the active room objects) but now write to setVariableHighlightOverlays.

WiredVariablesTabView drops the isVariableHighlightActive +
onToggleVariableHighlight props and consumes the store directly — same
shape as the previous tab-prop reductions on this branch. The toggle
button keeps the same UX (Highlight ↔ Undo) but no longer crosses the
prop boundary.

Direct benefit: closing and reopening the panel while a variable
highlight is active no longer flickers the overlays off and back on —
the active flag + the last-computed overlay set both persist in
zustand and the effect re-runs from the same starting point.

Tests: three new cases on the store (toggle via direct + updater,
overlay replace + clear, close/reopen persistence). 190/190 passing.

variableHighlightObjectsRef stays a useRef inside the component: it
tracks the live PIXI objects WiredSelectionVisualizer drew onto, used
only for the cleanup pass — refs don't trigger renders and don't need
to live in the store.
This commit is contained in:
simoleo89
2026-05-16 12:31:19 +02:00
parent 50fd908d5a
commit 0fc32a1e19
4 changed files with 71 additions and 12 deletions
@@ -77,8 +77,10 @@ export const WiredCreatorToolsView: FC<{}> = () =>
const setIsManagedGiveOpen = useWiredCreatorToolsUiStore(s => s.setIsManagedGiveOpen);
const [ managedGiveVariableItemId, setManagedGiveVariableItemId ] = useState(0);
const [ managedGiveValue, setManagedGiveValue ] = useState('0');
const [ isVariableHighlightActive, setIsVariableHighlightActive ] = useState(false);
const [ variableHighlightOverlays, setVariableHighlightOverlays ] = useState<VariableHighlightOverlay[]>([]);
const isVariableHighlightActive = useWiredCreatorToolsUiStore(s => s.isVariableHighlightActive);
const setIsVariableHighlightActive = useWiredCreatorToolsUiStore(s => s.setIsVariableHighlightActive);
const variableHighlightOverlays = useWiredCreatorToolsUiStore(s => s.variableHighlightOverlays);
const setVariableHighlightOverlays = useWiredCreatorToolsUiStore(s => s.setVariableHighlightOverlays);
const variableHighlightObjectsRef = useRef<Array<{ category: number; objectId: number; }>>([]);
const shouldPauseVariableSnapshotRefresh = (!!editingVariable || !!editingManagedHolderVariableId || isInspectionGiveOpen || isManagedGiveOpen);
const [ selectedVariableKeys, setSelectedVariableKeys ] = useState<Record<VariablesElementType, string>>({
@@ -3145,8 +3147,6 @@ export const WiredCreatorToolsView: FC<{}> = () =>
selectedVariableDefinition={ selectedVariableDefinition }
onPickVariable={ key => setSelectedVariableKeys(prev => ({ ...prev, [variablesType]: key })) }
canVariableHighlight={ canVariableHighlight }
isVariableHighlightActive={ isVariableHighlightActive }
onToggleVariableHighlight={ () => setIsVariableHighlightActive(value => !value) }
variableManageCanOpen={ variableManageCanOpen }
onOpenManagePanel={ () =>
{
@@ -10,8 +10,6 @@ export interface WiredVariablesTabViewProps
selectedVariableDefinition: VariableDefinition | null;
onPickVariable: (key: string) => void;
canVariableHighlight: boolean;
isVariableHighlightActive: boolean;
onToggleVariableHighlight: () => void;
variableManageCanOpen: boolean;
onOpenManagePanel: () => void;
selectedVariableProperties: { key: string; value: string; }[];
@@ -30,8 +28,6 @@ export const WiredVariablesTabView: FC<WiredVariablesTabViewProps> = ({
selectedVariableDefinition,
onPickVariable,
canVariableHighlight,
isVariableHighlightActive,
onToggleVariableHighlight,
variableManageCanOpen,
onOpenManagePanel,
selectedVariableProperties,
@@ -40,6 +36,8 @@ export const WiredVariablesTabView: FC<WiredVariablesTabViewProps> = ({
{
const variablesType = useWiredCreatorToolsUiStore(s => s.variablesType);
const setVariablesType = useWiredCreatorToolsUiStore(s => s.setVariablesType);
const isVariableHighlightActive = useWiredCreatorToolsUiStore(s => s.isVariableHighlightActive);
const setIsVariableHighlightActive = useWiredCreatorToolsUiStore(s => s.setIsVariableHighlightActive);
return (
<div className="p-3 min-h-[360px] flex gap-4">
@@ -83,7 +81,7 @@ export const WiredVariablesTabView: FC<WiredVariablesTabViewProps> = ({
<Button
disabled={ !canVariableHighlight }
variant="secondary"
onClick={ onToggleVariableHighlight }>
onClick={ () => setIsVariableHighlightActive(value => !value) }>
{ isVariableHighlightActive ? 'Undo' : 'Highlight' }
</Button>
<Button
@@ -22,7 +22,9 @@ const INITIAL = {
selectedFurniLiveState: null,
selectedUser: null,
selectedUserLiveState: null,
selectedUserActionVersion: 0
selectedUserActionVersion: 0,
isVariableHighlightActive: false,
variableHighlightOverlays: []
};
describe('useWiredCreatorToolsUiStore', () =>
@@ -56,6 +58,8 @@ describe('useWiredCreatorToolsUiStore', () =>
expect(state.selectedUser).toBeNull();
expect(state.selectedUserLiveState).toBeNull();
expect(state.selectedUserActionVersion).toBe(0);
expect(state.isVariableHighlightActive).toBe(false);
expect(state.variableHighlightOverlays).toEqual([]);
});
describe('setIsVisible', () =>
@@ -314,4 +318,42 @@ describe('useWiredCreatorToolsUiStore', () =>
expect(useWiredCreatorToolsUiStore.getState().selectedFurni).toEqual(furniSelection);
});
});
describe('variable highlight', () =>
{
const overlay = { itemId: 1, key: 'foo', x: 100, y: 200, screenX: 100, screenY: 200, value: '42', objectId: 7, category: 10 } as never;
it('setIsVariableHighlightActive accepts a direct boolean and a toggle updater', () =>
{
useWiredCreatorToolsUiStore.getState().setIsVariableHighlightActive(true);
expect(useWiredCreatorToolsUiStore.getState().isVariableHighlightActive).toBe(true);
useWiredCreatorToolsUiStore.getState().setIsVariableHighlightActive(prev => !prev);
expect(useWiredCreatorToolsUiStore.getState().isVariableHighlightActive).toBe(false);
useWiredCreatorToolsUiStore.getState().setIsVariableHighlightActive(prev => !prev);
expect(useWiredCreatorToolsUiStore.getState().isVariableHighlightActive).toBe(true);
});
it('setVariableHighlightOverlays replaces the overlay array', () =>
{
useWiredCreatorToolsUiStore.getState().setVariableHighlightOverlays([ overlay ]);
expect(useWiredCreatorToolsUiStore.getState().variableHighlightOverlays).toEqual([ overlay ]);
useWiredCreatorToolsUiStore.getState().setVariableHighlightOverlays([]);
expect(useWiredCreatorToolsUiStore.getState().variableHighlightOverlays).toEqual([]);
});
it('the highlight survives a panel close/reopen lifecycle', () =>
{
useWiredCreatorToolsUiStore.getState().setIsVariableHighlightActive(true);
useWiredCreatorToolsUiStore.getState().setVariableHighlightOverlays([ overlay ]);
useWiredCreatorToolsUiStore.getState().setIsVisible(false);
useWiredCreatorToolsUiStore.getState().setIsVisible(true);
expect(useWiredCreatorToolsUiStore.getState().isVariableHighlightActive).toBe(true);
expect(useWiredCreatorToolsUiStore.getState().variableHighlightOverlays).toEqual([ overlay ]);
});
});
});
@@ -1,6 +1,6 @@
import { createNitroStore } from '../../state/createNitroStore';
import { createEmptyMonitorSnapshot } from './WiredCreatorTools.helpers';
import { InspectionElementType, InspectionFurniLiveState, InspectionFurniSelection, InspectionUserLiveState, InspectionUserSelection, MonitorSnapshot, VariablesElementType, WiredToolsTab } from './WiredCreatorTools.types';
import { InspectionElementType, InspectionFurniLiveState, InspectionFurniSelection, InspectionUserLiveState, InspectionUserSelection, MonitorSnapshot, VariableHighlightOverlay, VariablesElementType, WiredToolsTab } from './WiredCreatorTools.types';
type MonitorSeverityFilter = 'ALL' | 'ERROR' | 'WARNING';
type Updater<T> = T | ((prev: T) => T);
@@ -55,6 +55,16 @@ interface WiredCreatorToolsUiState
selectedUserLiveState: InspectionUserLiveState | null;
selectedUserActionVersion: number;
/**
* Variable highlight feature: the toggle UI flag (`isActive`) plus
* the computed screen-space overlays the parent's effect populates
* from `WiredSelectionVisualizer` + `GetRoomObjectScreenLocation`.
* Stored together so a panel close/reopen cycle keeps the active
* highlight visible instead of re-toggling from scratch.
*/
isVariableHighlightActive: boolean;
variableHighlightOverlays: VariableHighlightOverlay[];
setIsVisible: (next: Updater<boolean>) => void;
setActiveTab: (next: WiredToolsTab) => void;
setInspectionType: (next: InspectionElementType) => void;
@@ -81,6 +91,9 @@ interface WiredCreatorToolsUiState
setSelectedUser: (next: InspectionUserSelection | null) => void;
setSelectedUserLiveState: (next: Updater<InspectionUserLiveState | null>) => void;
setSelectedUserActionVersion: (next: Updater<number>) => void;
setIsVariableHighlightActive: (next: Updater<boolean>) => void;
setVariableHighlightOverlays: (next: VariableHighlightOverlay[]) => void;
}
export const useWiredCreatorToolsUiStore = createNitroStore<WiredCreatorToolsUiState>()((set) => ({
@@ -110,6 +123,9 @@ export const useWiredCreatorToolsUiStore = createNitroStore<WiredCreatorToolsUiS
selectedUserLiveState: null,
selectedUserActionVersion: 0,
isVariableHighlightActive: false,
variableHighlightOverlays: [],
setIsVisible: (next) => set(state => ({ isVisible: apply(state.isVisible, next) })),
setActiveTab: (next) => set({ activeTab: next }),
setInspectionType: (next) => set({ inspectionType: next }),
@@ -135,5 +151,8 @@ export const useWiredCreatorToolsUiStore = createNitroStore<WiredCreatorToolsUiS
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) }))
setSelectedUserActionVersion: (next) => set(state => ({ selectedUserActionVersion: apply(state.selectedUserActionVersion, next) })),
setIsVariableHighlightActive: (next) => set(state => ({ isVariableHighlightActive: apply(state.isVariableHighlightActive, next) })),
setVariableHighlightOverlays: (next) => set({ variableHighlightOverlays: next })
}));