aframe-inspector
Version:
A visual inspector tool for A-Frame.
310 lines (265 loc) • 8.05 kB
JavaScript
import { createRoot } from 'react-dom/client';
import Events from './lib/Events';
import { Viewport } from './lib/viewport';
import { AssetsLoader } from './lib/assetsLoader';
import { Shortcuts } from './lib/shortcuts';
import Main from './components/Main';
import { initCameras } from './lib/cameras';
import { createEntity } from './lib/entity';
import { Config } from './lib/config';
import { GLTFExporter } from 'three/addons/exporters/GLTFExporter';
import './style/index.styl';
function Inspector(configOverrides) {
this.assetsLoader = new AssetsLoader();
this.config = new Config(configOverrides);
this.exporters = { gltf: new GLTFExporter() };
this.history = require('./lib/history');
this.isFirstOpen = true;
this.modules = {};
this.opened = false;
// Wait for stuff.
const doInit = () => {
if (!AFRAME.scenes.length) {
setTimeout(() => {
doInit();
}, 100);
return;
}
this.sceneEl = AFRAME.scenes[0];
if (this.sceneEl.hasLoaded) {
this.init();
return;
}
this.sceneEl.addEventListener('loaded', this.init.bind(this), {
once: true
});
};
doInit();
}
Inspector.prototype = {
init: function () {
// Wait for camera.
if (!this.sceneEl.camera) {
this.sceneEl.addEventListener(
'camera-set-active',
() => {
this.init();
},
{ once: true }
);
return;
}
this.container = document.querySelector('.a-canvas');
initCameras(this);
this.initUI();
},
initUI: function () {
Shortcuts.init(this);
this.initEvents();
this.selected = null;
// Init React.
const div = document.createElement('div');
div.id = 'aframeInspector';
div.setAttribute('data-aframe-inspector', 'app');
document.body.appendChild(div);
const root = createRoot(div);
root.render(<Main />);
this.scene = this.sceneEl.object3D;
this.helpers = {};
this.sceneHelpers = new THREE.Scene();
this.sceneHelpers.userData.source = 'INSPECTOR';
this.sceneHelpers.visible = true;
this.inspectorActive = false;
this.viewport = new Viewport(this);
this.sceneEl.object3D.traverse((node) => {
this.addHelper(node);
});
this.scene.add(this.sceneHelpers);
this.open();
},
removeObject: function (object) {
// Remove just the helper as the object will be deleted by A-Frame
this.removeHelpers(object);
Events.emit('objectremove', object);
},
addHelper: function (object) {
let helper;
if (object instanceof THREE.Camera) {
this.cameraHelper = helper = new THREE.CameraHelper(object);
} else if (object instanceof THREE.PointLight) {
helper = new THREE.PointLightHelper(object, 1);
} else if (object instanceof THREE.DirectionalLight) {
helper = new THREE.DirectionalLightHelper(object, 1);
} else if (object instanceof THREE.SpotLight) {
helper = new THREE.SpotLightHelper(object, 1);
} else if (object instanceof THREE.HemisphereLight) {
helper = new THREE.HemisphereLightHelper(object, 1);
} else if (object instanceof THREE.SkinnedMesh) {
helper = new THREE.SkeletonHelper(object);
} else {
// no helper for this object type
return;
}
helper.visible = false;
this.sceneHelpers.add(helper);
this.helpers[object.uuid] = helper;
// SkeletonHelper doesn't have an update method
if (helper.update) {
helper.update();
}
},
removeHelpers: function (object) {
object.traverse((node) => {
const helper = this.helpers[node.uuid];
if (helper) {
this.sceneHelpers.remove(helper);
helper.dispose();
delete this.helpers[node.uuid];
Events.emit('helperremove', this.helpers[node.uuid]);
}
});
},
selectEntity: function (entity, emit) {
this.selectedEntity = entity;
if (entity) {
this.select(entity.object3D);
} else {
this.select(null);
}
if (emit === undefined) {
Events.emit('entityselect', entity);
}
// Update helper visibilities.
for (const id in this.helpers) {
this.helpers[id].visible = false;
}
if (entity === this.sceneEl) {
return;
}
if (entity) {
entity.object3D.traverse((node) => {
if (this.helpers[node.uuid]) {
this.helpers[node.uuid].visible = true;
}
});
}
},
initEvents: function () {
// Remove inspector component to properly unregister keydown listener when the inspector is loaded via a script tag,
// otherwise the listener will be registered twice and we can't toggle the inspector from viewer mode with the shortcut.
this.sceneEl.removeAttribute('inspector');
window.addEventListener('keydown', (evt) => {
// Alt + Ctrl + i: Shorcut to toggle the inspector
const shortcutPressed =
evt.keyCode === 73 &&
((evt.ctrlKey && evt.altKey) || evt.getModifierState('AltGraph'));
if (shortcutPressed) {
this.toggle();
}
});
Events.on('entityselect', (entity) => {
this.selectEntity(entity, false);
});
Events.on('inspectortoggle', (active) => {
this.inspectorActive = active;
this.sceneHelpers.visible = this.inspectorActive;
});
Events.on('entitycreate', (definition) => {
createEntity(definition, (entity) => {
this.selectEntity(entity);
});
});
document.addEventListener('child-detached', (event) => {
const entity = event.detail.el;
AFRAME.INSPECTOR.removeObject(entity.object3D);
});
},
selectById: function (id) {
if (id === this.camera.id) {
this.select(this.camera);
return;
}
const object = this.scene.getObjectById(id);
if (object) {
this.select(object);
}
},
/**
* Change to select object.
*/
select: function (object3D) {
if (this.selected === object3D) {
return;
}
this.selected = object3D;
Events.emit('objectselect', object3D);
},
deselect: function () {
this.select(null);
},
/**
* Toggle the editor
*/
toggle: function () {
if (this.opened) {
this.close();
} else {
this.open();
}
},
/**
* Open the editor UI
*/
open: function (focusEl) {
this.opened = true;
Events.emit('inspectortoggle', true);
if (this.sceneEl.hasAttribute('embedded')) {
// Remove embedded styles, but keep track of it.
this.sceneEl.removeAttribute('embedded');
this.sceneEl.setAttribute('aframe-inspector-removed-embedded');
}
document.body.classList.add('aframe-inspector-opened');
this.sceneEl.resize();
this.sceneEl.pause();
this.sceneEl.exitVR();
Shortcuts.enable();
// Trick scene to run the cursor tick.
this.sceneEl.isPlaying = true;
this.cursor.play();
if (
!focusEl &&
this.isFirstOpen &&
AFRAME.utils.getUrlParameter('inspector')
) {
// Focus entity with URL parameter on first open.
focusEl = document.getElementById(
AFRAME.utils.getUrlParameter('inspector')
);
}
if (focusEl) {
this.selectEntity(focusEl);
Events.emit('objectfocus', focusEl.object3D);
}
this.isFirstOpen = false;
},
/**
* Closes the editor and gives the control back to the scene
* @return {[type]} [description]
*/
close: function () {
this.opened = false;
Events.emit('inspectortoggle', false);
// Untrick scene when we enabled this to run the cursor tick.
this.sceneEl.isPlaying = false;
this.sceneEl.play();
this.cursor.pause();
if (this.sceneEl.hasAttribute('aframe-inspector-removed-embedded')) {
this.sceneEl.setAttribute('embedded', '');
this.sceneEl.removeAttribute('aframe-inspector-removed-embedded');
}
document.body.classList.remove('aframe-inspector-opened');
this.sceneEl.resize();
Shortcuts.disable();
}
};
AFRAME.INSPECTOR = new Inspector(window.AFRAME_INSPECTOR_CONFIG);