src/managers/state-manager.ts
A singleton manager for managing the scene's state.
constructor()
|
|
Defined in src/managers/state-manager.ts:34
|
|
Create the state manager. |
| activeCamera |
Type : Camera
|
|
Defined in src/managers/state-manager.ts:27
|
|
The active camera. |
| clippingEnabled |
Type : unknown
|
Default value : new ActiveVariable(false)
|
|
Defined in src/managers/state-manager.ts:21
|
|
Whether the clipping is enabled or not. |
| eventDisplay |
Type : EventDisplay
|
|
Defined in src/managers/state-manager.ts:29
|
|
The event display. |
| eventMetadata |
Type : literal type
|
Default value : {
runNumber: '000',
eventNumber: '000',
}
|
|
Defined in src/managers/state-manager.ts:31
|
|
Current loaded event's metadata. |
| Private Static instance |
Type : StateManager
|
|
Defined in src/managers/state-manager.ts:17
|
|
Instance of the state manager. |
| openingClippingAngle |
Type : unknown
|
Default value : new ActiveVariable(0)
|
|
Defined in src/managers/state-manager.ts:25
|
|
Opening angle of the clipping. |
| phoenixMenuRoot |
Type : PhoenixMenuNode
|
|
Defined in src/managers/state-manager.ts:19
|
|
Root node of the phoenix menu. |
| startClippingAngle |
Type : unknown
|
Default value : new ActiveVariable(0)
|
|
Defined in src/managers/state-manager.ts:23
|
|
Starting angle of the clipping. |
| Private getActiveCutsAsJSON |
getActiveCutsAsJSON()
|
|
Defined in src/managers/state-manager.ts:116
|
|
Serialize active cuts from PhoenixMenuUI into a plain JSON-safe object. Only collections with at least one active cut bound are included. Returns an empty object if no cuts are active or UI is unavailable.
Returns :
CutStateJSON
|
| Static getInstance |
getInstance()
|
|
Defined in src/managers/state-manager.ts:51
|
|
Get the instance of state manager.
Returns :
StateManager
The state manager instance. |
| getOpeningClippingAngle |
getOpeningClippingAngle()
|
|
Defined in src/managers/state-manager.ts:275
|
|
Get the opening angle of clipping.
Returns :
number
The opening angle of clipping. |
| getStartClippingAngle |
getStartClippingAngle()
|
|
Defined in src/managers/state-manager.ts:259
|
|
Get the start clipping angle of clipping.
Returns :
number
The starting angle of clipping. |
| getStateAsJSON |
getStateAsJSON()
|
|
Defined in src/managers/state-manager.ts:89
|
|
Get the current state of the event display as a JSON object. Now includes active cut state so Save State preserves filters.
Returns :
literal type
The state object with menu, camera, clipping, and cut data. |
| loadStateFromJSON | ||||||||
loadStateFromJSON(json: string | literal type)
|
||||||||
|
Defined in src/managers/state-manager.ts:155
|
||||||||
|
Load the state from JSON.
Parameters :
Returns :
void
|
| resetForViewTransition |
resetForViewTransition()
|
|
Defined in src/managers/state-manager.ts:298
|
|
Reset state manager for view transitions. Clears stale references.
Returns :
void
|
| Private restoreCutsFromJSON | ||||||||
restoreCutsFromJSON(cutsJSON: CutStateJSON)
|
||||||||
|
Defined in src/managers/state-manager.ts:217
|
||||||||
|
Deserialize cut state from a saved JSON file, register the cuts back into PhoenixMenuUI, and re-apply them to the currently loaded event.
Parameters :
Returns :
void
|
| saveStateAsJSON |
saveStateAsJSON()
|
|
Defined in src/managers/state-manager.ts:142
|
|
Save the state of the event display as JSON.
Returns :
void
|
| setCamera | ||||||||
setCamera(camera: Camera)
|
||||||||
|
Defined in src/managers/state-manager.ts:283
|
||||||||
|
Set the scene camera for state.
Parameters :
Returns :
void
|
| setClippingEnabled | ||||||||
setClippingEnabled(clipping: boolean)
|
||||||||
|
Defined in src/managers/state-manager.ts:243
|
||||||||
|
Set the state of clipping.
Parameters :
Returns :
void
|
| setEventDisplay | ||||||||
setEventDisplay(eventDisplay: EventDisplay)
|
||||||||
|
Defined in src/managers/state-manager.ts:291
|
||||||||
|
Set the event display.
Parameters :
Returns :
void
|
| setOpeningClippingAngle | ||||||||
setOpeningClippingAngle(angle: number)
|
||||||||
|
Defined in src/managers/state-manager.ts:267
|
||||||||
|
Set the opening angle of clipping.
Parameters :
Returns :
void
|
| setPhoenixMenuRoot | ||||||||
setPhoenixMenuRoot(phoenixMenuRoot: PhoenixMenuNode)
|
||||||||
|
Defined in src/managers/state-manager.ts:59
|
||||||||
|
Set the root node of Phoenix menu.
Parameters :
Returns :
void
|
| setStartClippingAngle | ||||||||
setStartClippingAngle(angle: number)
|
||||||||
|
Defined in src/managers/state-manager.ts:251
|
||||||||
|
Set the start clipping angle of clipping.
Parameters :
Returns :
void
|
import { EventDisplay } from '../event-display';
import { Camera } from 'three';
import { PhoenixMenuNode } from './ui-manager/phoenix-menu/phoenix-menu-node';
import { loadFile, saveFile } from '../helpers/file';
import { ActiveVariable } from '../helpers/active-variable';
import { Cut, CutJSON } from '../lib/models/cut.model';
/** Map of collection name to array of serialized cuts, for JSON persistence. */
export interface CutStateJSON {
[collectionName: string]: CutJSON[];
}
/**
* A singleton manager for managing the scene's state.
*/
export class StateManager {
/** Instance of the state manager. */
private static instance: StateManager;
/** Root node of the phoenix menu. */
phoenixMenuRoot: PhoenixMenuNode;
/** Whether the clipping is enabled or not. */
clippingEnabled = new ActiveVariable(false);
/** Starting angle of the clipping. */
startClippingAngle = new ActiveVariable(0);
/** Opening angle of the clipping. */
openingClippingAngle = new ActiveVariable(0);
/** The active camera. */
activeCamera: Camera;
/** The event display. */
eventDisplay: EventDisplay;
/** Current loaded event's metadata. */
eventMetadata: { runNumber: string; eventNumber: string } = {
runNumber: '000',
eventNumber: '000',
};
/**
* Create the state manager.
* @returns The state manager instance.
*/
constructor() {
if (StateManager.instance === undefined) {
StateManager.instance = this;
}
return StateManager.instance;
}
/**
* Get the instance of state manager.
* @returns The state manager instance.
*/
static getInstance(): StateManager {
return StateManager.instance;
}
/**
* Set the root node of Phoenix menu.
* @param phoenixMenuRoot Phoenix menu root node.
*/
setPhoenixMenuRoot(phoenixMenuRoot: PhoenixMenuNode) {
this.phoenixMenuRoot = phoenixMenuRoot;
if (this.phoenixMenuRoot) {
// Add save and load config buttons to the root node
this.phoenixMenuRoot
.addConfig({
type: 'button',
label: 'Save state',
onClick: () => {
this.saveStateAsJSON();
},
})
.addConfig({
type: 'button',
label: 'Load state',
onClick: () => {
loadFile((data) => {
this.loadStateFromJSON(JSON.parse(data));
});
},
});
}
}
/**
* Get the current state of the event display as a JSON object.
* Now includes active cut state so Save State preserves filters.
* @returns The state object with menu, camera, clipping, and cut data.
*/
getStateAsJSON(): { [key: string]: any } {
const controls = this.eventDisplay
?.getThreeManager()
?.getControlsManager()
?.getMainControls();
return {
phoenixMenu: this.phoenixMenuRoot?.getNodeState(),
eventDisplay: {
cameraPosition: this.activeCamera?.position.toArray(),
cameraTarget: controls?.target?.toArray(),
startClippingAngle: this.clippingEnabled.value
? this.startClippingAngle.value
: null,
openingClippingAngle: this.clippingEnabled.value
? this.openingClippingAngle.value
: null,
},
cuts: this.getActiveCutsAsJSON(),
};
}
/**
* Serialize active cuts from PhoenixMenuUI into a plain JSON-safe object.
* Only collections with at least one active cut bound are included.
* Returns an empty object if no cuts are active or UI is unavailable.
*/
private getActiveCutsAsJSON(): CutStateJSON {
const phoenixMenuUI = this.eventDisplay
?.getUIManager()
?.getPhoenixMenuUI?.();
if (!phoenixMenuUI) {
return {};
}
const registry = phoenixMenuUI.getCollectionCuts(); // This returns object { [name: string]: Cut[] }
const result: CutStateJSON = {};
Object.entries(registry).forEach(([collectionName, cuts]) => {
const activeCuts = cuts.filter(
(cut) => cut.minCutActive || cut.maxCutActive,
);
if (activeCuts.length > 0) {
result[collectionName] = activeCuts.map((cut) => cut.toJSON());
}
});
return result;
}
/**
* Save the state of the event display as JSON.
*/
saveStateAsJSON() {
const state = this.getStateAsJSON();
saveFile(
JSON.stringify(state),
`run${this.eventMetadata.runNumber}_evt${this.eventMetadata.eventNumber}.json`,
);
}
/**
* Load the state from JSON.
* @param json JSON for state.
*/
loadStateFromJSON(json: string | { [key: string]: any }) {
const jsonData: { [key: string]: any } =
typeof json === 'string' ? JSON.parse(json) : json;
if (jsonData['phoenixMenu'] && this.phoenixMenuRoot) {
console.log('StateManager: Processing phoenixMenu configuration');
this.phoenixMenuRoot.loadStateFromJSON(jsonData['phoenixMenu']);
this.phoenixMenuRoot.configActive = false;
}
if (jsonData['eventDisplay']) {
console.log('StateManager: Processing eventDisplay configuration');
if (jsonData['eventDisplay']?.['cameraPosition']) {
this.activeCamera.position.fromArray(
jsonData['eventDisplay']['cameraPosition'],
);
}
if (jsonData['eventDisplay']?.['cameraTarget']) {
const controls = this.eventDisplay
?.getThreeManager()
?.getControlsManager()
?.getMainControls();
if (controls) {
controls.target.fromArray(jsonData['eventDisplay']['cameraTarget']);
controls.update();
}
}
const startAngle = jsonData['eventDisplay']?.['startClippingAngle'];
const openingAngle = jsonData['eventDisplay']?.['openingClippingAngle'];
if (startAngle || openingAngle) {
this.setClippingEnabled(true);
this.eventDisplay.getUIManager().setClipping(true);
if (startAngle) {
this.eventDisplay
.getUIManager()
.rotateStartAngleClipping(
jsonData['eventDisplay']['startClippingAngle'],
);
}
if (openingAngle) {
this.eventDisplay
.getUIManager()
.rotateOpeningAngleClipping(
jsonData['eventDisplay']['openingClippingAngle'],
);
}
}
}
// Restore cut state if present in the saved JSON
if (jsonData['cuts']) {
this.restoreCutsFromJSON(jsonData['cuts'] as CutStateJSON);
}
}
/**
* Deserialize cut state from a saved JSON file, register the cuts back
* into PhoenixMenuUI, and re-apply them to the currently loaded event.
* @param cutsJSON The cuts section of a loaded state JSON file.
*/
private restoreCutsFromJSON(cutsJSON: CutStateJSON): void {
const phoenixMenuUI = this.eventDisplay
?.getUIManager()
?.getPhoenixMenuUI?.();
if (!phoenixMenuUI) {
console.warn(
'StateManager: Cannot restore cuts — PhoenixMenuUI not available.',
);
return;
}
for (const [collectionName, cutJSONArray] of Object.entries(cutsJSON)) {
if (Array.isArray(cutJSONArray) && cutJSONArray.length > 0) {
const cuts = cutJSONArray.map((c) => Cut.fromJSON(c));
phoenixMenuUI.setCollectionCuts(collectionName, cuts);
}
}
phoenixMenuUI.reapplyCollectionCuts();
}
/**
* Set the state of clipping.
* @param clipping Whether the clipping is enabled or not.
*/
setClippingEnabled(clipping: boolean) {
this.clippingEnabled.update(clipping);
}
/**
* Set the start clipping angle of clipping.
* @param angle Angle for clipping.
*/
setStartClippingAngle(angle: number) {
this.startClippingAngle.update(angle);
}
/**
* Get the start clipping angle of clipping.
* @returns The starting angle of clipping.
*/
getStartClippingAngle(): number {
return this.startClippingAngle.value ?? 0.0;
}
/**
* Set the opening angle of clipping.
* @param angle Angle for clipping.
*/
setOpeningClippingAngle(angle: number) {
this.openingClippingAngle.update(angle);
}
/**
* Get the opening angle of clipping.
* @returns The opening angle of clipping.
*/
getOpeningClippingAngle(): number {
return this.openingClippingAngle.value ?? 0.0;
}
/**
* Set the scene camera for state.
* @param camera The camera.
*/
setCamera(camera: Camera) {
this.activeCamera = camera;
}
/**
* Set the event display.
* @param eventDisplay The event display.
*/
setEventDisplay(eventDisplay: EventDisplay) {
this.eventDisplay = eventDisplay;
}
/**
* Reset state manager for view transitions. Clears stale references.
*/
resetForViewTransition() {
this.phoenixMenuRoot = undefined;
this.activeCamera = undefined;
this.clippingEnabled = new ActiveVariable(false);
this.startClippingAngle = new ActiveVariable(0);
this.openingClippingAngle = new ActiveVariable(0);
this.eventMetadata = { runNumber: '000', eventNumber: '000' };
}
}