Fix diagram text sync, resize handle positioning, and PouchDB delete handling

- Fix diagramObject text setter to update entity before notifying observers
- Improve ResizeGizmo handle positioning directly at corners/faces with constant screen-size scaling
- Fix PouchDB sync to handle deleted documents using _id field for compatibility

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Michael Mainguy 2026-01-13 11:16:43 -06:00
parent 960c64984e
commit 8c2b7f9c7d
4 changed files with 47 additions and 32 deletions

View File

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

View File

@ -227,13 +227,24 @@ export class DiagramObject {
if (this._labelBack) {
this._labelBack.dispose();
}
if (this._diagramEntity.text != value) {
const textChanged = this._diagramEntity.text != value;
// Update the entity text FIRST (before notifying observers)
this._diagramEntity.text = value;
// Also update mesh metadata to keep in sync with diagramEntity getter
if (this._mesh && this._mesh.metadata) {
this._mesh.metadata.text = value;
}
// THEN notify observers with the UPDATED entity
if (textChanged) {
this._eventObservable.notifyObservers({
type: DiagramEventType.MODIFY,
entity: this._diagramEntity
}, DiagramEventObserverMask.TO_DB);
}
this._diagramEntity.text = value;
this._label = createLabel(value);
this._label.parent = this._baseTransform;
this._labelBack = new InstancedMesh('labelBack' + value, (this._label as Mesh));

View File

@ -108,6 +108,10 @@ export class ResizeGizmo {
* Create corner and face handles
*/
private createHandles(): void {
// Ensure world matrix and bounding info are current
this._targetMesh.computeWorldMatrix(true);
this._targetMesh.refreshBoundingInfo();
// Get bounding box for positioning
const targetBoundingInfo = this._targetMesh.getBoundingInfo();
const boundingBox = targetBoundingInfo.boundingBox;
@ -116,10 +120,10 @@ export class ResizeGizmo {
const innerCorners = boundingBox.vectorsWorld;
const worldMatrix = this._targetMesh.getWorldMatrix();
// Calculate handle size once (based on corner distance)
const handleSize = innerCorners[0].subtract(bboxCenter).length() * .5;
// Unit size - actual visual size controlled by updateHandleScaling()
const handleSize = 1.0;
// Create corner handles
// Create corner handles - positioned directly at bounding box corners
CORNER_POSITIONS.forEach((cornerDef, index) => {
const cornerPos = innerCorners[index];
@ -129,10 +133,8 @@ export class ResizeGizmo {
this._utilityLayer.utilityLayerScene
);
// Position outward from center so handle corner touches bounding box corner
const direction = cornerPos.subtract(bboxCenter).normalize();
const offset = direction.scale(handleSize * Math.sqrt(3) / 2);
handleMesh.position = cornerPos.add(offset);
// Position handle at the corner (scaling will make it small)
handleMesh.position = cornerPos.clone();
handleMesh.rotationQuaternion = this._targetMesh.absoluteRotationQuaternion;
handleMesh.material = this._handleMaterial;
handleMesh.isPickable = true;
@ -156,10 +158,8 @@ export class ResizeGizmo {
this._utilityLayer.utilityLayerScene
);
// Position outward from center so handle touches face center
const direction = faceCenterWorld.subtract(bboxCenter).normalize();
const offset = direction.scale(handleSize * Math.sqrt(3) / 2);
handleMesh.position = faceCenterWorld.add(offset);
// Position handle at the face center (scaling will make it small)
handleMesh.position = faceCenterWorld.clone();
handleMesh.rotationQuaternion = this._targetMesh.absoluteRotationQuaternion;
handleMesh.material = this._handleMaterial;
handleMesh.isPickable = true;
@ -197,15 +197,20 @@ export class ResizeGizmo {
}
/**
* Update handle scaling based on camera distance for consistent visual size
* Update handle scaling based on camera distance for constant screen size.
* Handles will appear the same visual size regardless of distance.
*/
private updateHandleScaling(): void {
const camera = this._scene.activeCamera;
if (!camera) return;
// Target angular size - tune this for desired visual size
// 0.03 means handles appear ~3cm at 1m distance, ~6cm at 2m, etc.
const targetAngularSize = 0.03;
for (const handle of this._handles) {
const distance = Vector3.Distance(camera.globalPosition, handle.position);
const scaleFactor = distance; // Adjust multiplier to control visual size
const scaleFactor = distance * targetAngularSize;
handle.scaling = new Vector3(scaleFactor, scaleFactor, scaleFactor);
}
}
@ -482,27 +487,24 @@ export class ResizeGizmo {
* Update handle positions and sizes to match current target mesh bounding box
*/
private updateHandleTransforms(): void {
// Ensure world matrix and bounding info are current
this._targetMesh.computeWorldMatrix(true);
this._targetMesh.refreshBoundingInfo();
const targetBoundingInfo = this._targetMesh.getBoundingInfo();
const boundingBox = targetBoundingInfo.boundingBox;
const bboxCenter = boundingBox.centerWorld;
const extents = boundingBox.extendSize;
const innerCorners = boundingBox.vectorsWorld;
const worldMatrix = this._targetMesh.getWorldMatrix();
// Recalculate handle size based on new bounding box
const newHandleSize = innerCorners[0].subtract(bboxCenter).length() * .2;
let handleIndex = 0;
// Update corner handles (first 8 handles)
// Update corner handles (first 8 handles) - position at corners
for (let i = 0; i < CORNER_POSITIONS.length; i++) {
const handle = this._handles[handleIndex];
const cornerPos = innerCorners[i];
// Update position
const direction = cornerPos.subtract(bboxCenter).normalize();
const offset = direction.scale(newHandleSize * Math.sqrt(3) / 2);
handle.position = cornerPos.add(offset);
handle.position = cornerPos.clone();
handle.rotationQuaternion = this._targetMesh.absoluteRotationQuaternion;
handleIndex++;
@ -520,10 +522,7 @@ export class ResizeGizmo {
);
const faceCenterWorld = Vector3.TransformCoordinates(localFacePos, worldMatrix);
// Update position
const direction = faceCenterWorld.subtract(bboxCenter).normalize();
const offset = direction.scale(newHandleSize * Math.sqrt(3) / 2);
handle.position = faceCenterWorld.add(offset);
handle.position = faceCenterWorld.clone();
handle.rotationQuaternion = this._targetMesh.absoluteRotationQuaternion;
handleIndex++;

View File

@ -61,9 +61,14 @@ export class PouchData {
if (info.direction === 'pull' && info.change && info.change.docs) {
info.change.docs.forEach((doc) => {
if (doc._deleted) {
this.onDBEntityRemoveObservable.notifyObservers(doc);
} else if (doc.id && doc.id !== 'metadata') {
this.onDBEntityUpdateObservable.notifyObservers(doc);
// PouchDB deleted documents only have _id, not our custom id field
// Create an entity with id from _id for the remove handler
const entity = { id: doc._id, ...doc };
this.onDBEntityRemoveObservable.notifyObservers(entity);
} else if ((doc.id || doc._id) && (doc.id || doc._id) !== 'metadata') {
// Accept both id and _id for compatibility
const entity = doc.id ? doc : { id: doc._id, ...doc };
this.onDBEntityUpdateObservable.notifyObservers(entity);
}
});
}