Migrate from legacy config to new AppConfig singleton system

Remove dual config system and migrate all code to use appConfigInstance.

**Phase 1: Update VR Controller Code**
- snapAll.ts: Replace getAppConfig() with appConfigInstance.current
  - Use `rotateSnap > 0` instead of rotationSnapEnabled flag
  - Use `locationSnap > 0` instead of locationSnapEnabled flag
  - Remove parseFloat() calls (values already numbers)
- groundMeshObserver.ts: Direct property replacement
  - flyModeEnabled → flyMode
  - snapTurnSnap → turnSnap (remove parseFloat)
- customPhysics.ts: Add enabled checks and update
  - Add `> 0` checks (was applying unconditionally)
  - Use locationSnap and rotateSnap directly

**Phase 2: Remove Legacy Config Bridge**
- vrConfigPanel.ts: Remove syncLegacyConfig() method and all calls
- configModal.tsx: Remove legacy localStorage 'config' writes

**Phase 3: Cleanup**
- appConfig.ts: Remove legacy code (ConfigType, getAppConfig(), setAppConfig())
- Remove unused log import

**Benefits:**
- Eliminates dual config system confusion
- Fixes precision error from string "0" values
- Single source of truth via appConfigInstance
- Reactive updates via Observable pattern
- Cleaner, simpler codebase

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Michael Mainguy 2025-11-18 13:20:18 -06:00
parent 3cf3d996dc
commit c66da87401
7 changed files with 36 additions and 107 deletions

View File

@ -1,7 +1,7 @@
{
"name": "immersive",
"private": true,
"version": "0.0.8-28",
"version": "0.0.8-30",
"type": "module",
"license": "MIT",
"engines": {

View File

@ -1,23 +1,23 @@
import {TransformNode, Vector3} from "@babylonjs/core";
import {getAppConfig} from "../../util/appConfig";
import {appConfigInstance} from "../../util/appConfig";
import {snapRotateVal} from "../../util/functions/snapRotateVal";
import {snapGridVal} from "../../util/functions/snapGridVal";
export function snapAll(node: TransformNode, pickPoint: Vector3) {
const config = getAppConfig();
const config = appConfigInstance.current;
const transform = new TransformNode('temp', node.getScene());
transform.position = pickPoint;
node.setParent(transform);
if (config.rotationSnapEnabled) {
node.rotation = snapRotateVal(node.absoluteRotationQuaternion.toEulerAngles(), parseFloat(config.rotationSnap));
if (config.rotateSnap > 0) {
node.rotation = snapRotateVal(node.absoluteRotationQuaternion.toEulerAngles(), config.rotateSnap);
}
if (config.locationSnapEnabled) {
transform.position = snapGridVal(transform.absolutePosition, parseFloat(config.locationSnap));
if (config.locationSnap > 0) {
transform.position = snapGridVal(transform.absolutePosition, config.locationSnap);
}
node.setParent(null);
if (config.locationSnapEnabled) {
node.position = snapGridVal(node.absolutePosition, parseFloat(config.locationSnap));
if (config.locationSnap > 0) {
node.position = snapGridVal(node.absolutePosition, config.locationSnap);
}
transform.dispose();

View File

@ -335,12 +335,10 @@ export class VRConfigPanel {
const lastValue = appConfigInstance.current.locationSnap || 0.1;
appConfigInstance.setGridSnap(lastValue > 0 ? lastValue : 0.1);
this.updateLocationSnapButtonStates(lastValue);
this.syncLegacyConfig();
} else {
// Disable by setting to 0
appConfigInstance.setGridSnap(0);
this.updateLocationSnapButtonStates(0);
this.syncLegacyConfig();
}
});
@ -388,7 +386,6 @@ export class VRConfigPanel {
if (this._locationSnapEnabled) {
appConfigInstance.setGridSnap(snap.value);
this.updateLocationSnapButtonStates(snap.value);
this.syncLegacyConfig();
}
});
@ -457,12 +454,10 @@ export class VRConfigPanel {
const lastValue = appConfigInstance.current.rotateSnap || 90;
appConfigInstance.setRotateSnap(lastValue > 0 ? lastValue : 90);
this.updateRotationSnapButtonStates(lastValue);
this.syncLegacyConfig();
} else {
// Disable by setting to 0
appConfigInstance.setRotateSnap(0);
this.updateRotationSnapButtonStates(0);
this.syncLegacyConfig();
}
});
@ -510,7 +505,6 @@ export class VRConfigPanel {
if (this._rotationSnapEnabled) {
appConfigInstance.setRotateSnap(snap.value);
this.updateRotationSnapButtonStates(snap.value);
this.syncLegacyConfig();
}
});
@ -586,26 +580,4 @@ export class VRConfigPanel {
// - Snap turn UI update
// - Label rendering mode UI update
}
/**
* Sync changes to legacy config for backward compatibility
* Legacy config is used by snapAll.ts and other older code
*/
private syncLegacyConfig(): void {
const config = appConfigInstance.current;
const legacyConfig = {
locationSnap: config.locationSnap.toString(),
locationSnapEnabled: config.locationSnap > 0,
rotationSnap: config.rotateSnap.toString(),
rotationSnapEnabled: config.rotateSnap > 0,
snapTurnSnap: config.turnSnap.toString(),
snapTurnSnapEnabled: config.turnSnap > 0,
flyModeEnabled: config.flyMode,
labelRenderingMode: config.labelRenderingMode
};
localStorage.setItem('config', JSON.stringify(legacyConfig));
this._logger.debug('Synced legacy config', legacyConfig);
}
}

View File

@ -59,20 +59,6 @@ export default function ConfigModal({configOpened, closeConfig}) {
appConfigInstance.setGridSnap(parseFloat(locationSnap));
appConfigInstance.setRotateSnap(parseFloat(rotationSnap));
appConfigInstance.setTurnSnap(parseFloat(snapTurnSnap));
// Also update legacy config for backward compatibility
const legacyConfig = {
locationSnap: locationSnap,
locationSnapEnabled: locationSnapEnabled,
rotationSnap: rotationSnap,
rotationSnapEnabled: rotationSnapEnabled,
snapTurnSnap: snapTurnSnap,
snapTurnSnapEnabled: snapTurnSnapEnabled,
flyModeEnabled: flyModeEnabled,
labelRenderingMode: labelRenderingMode
};
localStorage.setItem('config', JSON.stringify(legacyConfig));
}, [locationSnap, locationSnapEnabled, rotationSnap, rotationSnapEnabled, snapTurnSnap, snapTurnSnapEnabled, flyModeEnabled, labelRenderingMode]);
return (
<Modal onClose={closeConfig} opened={configOpened}>

View File

@ -1,6 +1,6 @@
import {Observable} from "@babylonjs/core";
import {AppConfigType, LabelRenderingMode} from "./appConfigType";
import log from "loglevel";
export class AppConfig {
public readonly onConfigChangedObservable = new Observable<AppConfigType>();
private _currentConfig: AppConfigType;
@ -88,42 +88,4 @@ export class AppConfig {
// Singleton instance for app-wide configuration
// Use this instead of creating new AppConfig() instances
export const appConfigInstance = new AppConfig();
let defaultConfig: ConfigType =
{
locationSnap: '.1',
locationSnapEnabled: true,
rotationSnap: '90',
rotationSnapEnabled: true,
flyModeEnabled: true,
snapTurnSnap: '45',
snapTurnSnapEnabled: false
}
try {
const newConfig = JSON.parse(localStorage.getItem('config'));
defaultConfig = {...defaultConfig, ...newConfig};
} catch (e) {
}
export type ConfigType = {
locationSnap: string,
locationSnapEnabled: boolean,
rotationSnap: string,
rotationSnapEnabled: boolean,
flyModeEnabled: boolean,
snapTurnSnap: string,
snapTurnSnapEnabled: boolean
}
export function getAppConfig(): ConfigType {
return defaultConfig;
}
export function setAppConfig(config: ConfigType) {
const logger = log.getLogger('setAppConfig');
logger.debug('setting config', JSON.stringify(config));
localStorage.setItem('config', JSON.stringify(config));
}
export const appConfigInstance = new AppConfig();

View File

@ -3,7 +3,7 @@ import HavokPhysics from "@babylonjs/havok";
import {snapGridVal} from "./functions/snapGridVal";
import {snapRotateVal} from "./functions/snapRotateVal";
import {isDiagramEntity} from "../diagram/functions/isDiagramEntity";
import {getAppConfig} from "./appConfig";
import {appConfigInstance} from "./appConfig";
export class CustomPhysics {
private readonly scene: Scene;
@ -21,6 +21,7 @@ export class CustomPhysics {
scene.collisionsEnabled = true;
scene.onAfterPhysicsObservable.add(() => {
const config = appConfigInstance.current;
scene.meshes.forEach((mesh) => {
if (isDiagramEntity(mesh) && mesh.physicsBody) {
const body = mesh.physicsBody;
@ -29,18 +30,26 @@ export class CustomPhysics {
if (linearVelocity.length() < .1) {
body.disablePreStep = false;
const pos: Vector3 = body.getObjectCenterWorld();
const val: Vector3 = snapGridVal(pos,
parseFloat(getAppConfig().locationSnap));
body.transformNode.position.set(val.x, val.y, val.z);
const rot: Quaternion =
Quaternion.FromEulerVector(
snapRotateVal(body.transformNode.rotationQuaternion.toEulerAngles(),
parseFloat(getAppConfig().rotationSnap)))
body.transformNode.rotationQuaternion.set(
rot.x, rot.y, rot.z, rot.w
);
// Apply location snap if enabled
if (config.locationSnap > 0) {
const pos: Vector3 = body.getObjectCenterWorld();
const val: Vector3 = snapGridVal(pos, config.locationSnap);
body.transformNode.position.set(val.x, val.y, val.z);
}
// Apply rotation snap if enabled
if (config.rotateSnap > 0) {
const rot: Quaternion =
Quaternion.FromEulerVector(
snapRotateVal(body.transformNode.rotationQuaternion.toEulerAngles(),
config.rotateSnap));
body.transformNode.rotationQuaternion.set(
rot.x, rot.y, rot.z, rot.w
);
}
scene.onAfterRenderObservable.addOnce(() => {
body.disablePreStep = true;
});

View File

@ -4,7 +4,7 @@ import {WebController} from "../../controllers/webController";
import {Rigplatform} from "../../controllers/rigplatform";
import {DiagramManager} from "../../diagram/diagramManager";
import {Spinner} from "../../objects/spinner";
import {getAppConfig} from "../appConfig";
import {appConfigInstance} from "../appConfig";
import {Scene} from "@babylonjs/core";
@ -114,9 +114,9 @@ export async function groundMeshObserver(ground: AbstractMesh,
});
const rig = new Rigplatform(xr, diagramManager);
const config = getAppConfig();
rig.flyMode = config.flyModeEnabled;
rig.turnSnap = parseFloat(config.snapTurnSnap);
const config = appConfigInstance.current;
rig.flyMode = config.flyMode;
rig.turnSnap = config.turnSnap;
const webController = new WebController(ground.getScene(), rig, diagramManager);
// Set XR on diagram manager so toolbox can create exit button