From 78011215a73834701df4b8bdfb241a0755ad2a27 Mon Sep 17 00:00:00 2001 From: Vincent Fretin Date: Mon, 26 Aug 2024 09:43:58 +0200 Subject: [PATCH 01/49] add EntityAddComponentCommand and EntityRemoveComponentCommand --- .../components/components/AddComponent.js | 12 +++++-- src/editor/components/components/Component.js | 12 ++++--- .../lib/commands/EntityAddComponentCommand.js | 32 +++++++++++++++++++ .../commands/EntityRemoveComponentCommand.js | 32 +++++++++++++++++++ src/editor/lib/commands/index.js | 2 ++ 5 files changed, 82 insertions(+), 8 deletions(-) create mode 100644 src/editor/lib/commands/EntityAddComponentCommand.js create mode 100644 src/editor/lib/commands/EntityRemoveComponentCommand.js diff --git a/src/editor/components/components/AddComponent.js b/src/editor/components/components/AddComponent.js index b090f2db5..e451ac0eb 100644 --- a/src/editor/components/components/AddComponent.js +++ b/src/editor/components/components/AddComponent.js @@ -1,8 +1,8 @@ import React from 'react'; import PropTypes from 'prop-types'; -import Events from '../../lib/Events'; import Select from 'react-select'; import { sendMetric } from '../../services/ga'; +import { EntityAddComponentCommand } from '../../lib/commands'; export default class AddComponent extends React.Component { static propTypes = { @@ -25,8 +25,14 @@ export default class AddComponent extends React.Component { componentName = id ? `${componentName}__${id}` : componentName; } - entity.setAttribute(componentName, ''); - Events.emit('componentadd', { entity: entity, component: componentName }); + const command = new EntityAddComponentCommand( + AFRAME.INSPECTOR, + entity, + componentName, + '' + ); + AFRAME.INSPECTOR.execute(command); + sendMetric('Components', 'addComponent', componentName); }; diff --git a/src/editor/components/components/Component.js b/src/editor/components/components/Component.js index 3058c3cf4..ec19dac3e 100644 --- a/src/editor/components/components/Component.js +++ b/src/editor/components/components/Component.js @@ -6,6 +6,7 @@ import PropertyRow from './PropertyRow'; import React from 'react'; import { getComponentClipboardRepresentation } from '../../lib/entity'; import { sendMetric } from '../../services/ga'; +import { EntityRemoveComponentCommand } from '../../lib/commands'; const isSingleProperty = AFRAME.schema.isSingleProperty; @@ -81,11 +82,12 @@ export default class Component extends React.Component { if ( confirm('Do you really want to remove component `' + componentName + '`?') ) { - this.props.entity.removeAttribute(componentName); - Events.emit('componentremove', { - entity: this.props.entity, - component: componentName - }); + const command = new EntityRemoveComponentCommand( + AFRAME.INSPECTOR, + this.props.entity, + componentName + ); + AFRAME.INSPECTOR.execute(command); sendMetric('Components', 'removeComponent', componentName); } }; diff --git a/src/editor/lib/commands/EntityAddComponentCommand.js b/src/editor/lib/commands/EntityAddComponentCommand.js new file mode 100644 index 000000000..7720d17ab --- /dev/null +++ b/src/editor/lib/commands/EntityAddComponentCommand.js @@ -0,0 +1,32 @@ +import Events from '../Events'; +import { Command } from '../command.js'; + +export class EntityAddComponentCommand extends Command { + constructor(editor, entity, componentName, componentData) { + super(editor); + + this.type = 'EntityAddComponentCommand'; + this.name = 'Add Component'; + this.updatable = false; + + this.entity = entity; + this.componentName = componentName; + this.componentData = componentData; + } + + execute() { + this.entity.setAttribute(this.componentName, this.componentData); + Events.emit('componentadd', { + entity: this.entity, + component: this.componentName + }); + } + + undo() { + this.entity.removeAttribute(this.componentName); + Events.emit('componentremove', { + entity: this.entity, + component: this.componentName + }); + } +} diff --git a/src/editor/lib/commands/EntityRemoveComponentCommand.js b/src/editor/lib/commands/EntityRemoveComponentCommand.js new file mode 100644 index 000000000..d3206c16c --- /dev/null +++ b/src/editor/lib/commands/EntityRemoveComponentCommand.js @@ -0,0 +1,32 @@ +import Events from '../Events'; +import { Command } from '../command.js'; + +export class EntityRemoveComponentCommand extends Command { + constructor(editor, entity, componentName) { + super(editor); + + this.type = 'EntityRemoveComponentCommand'; + this.name = 'Remove Component'; + this.updatable = false; + + this.entity = entity; + this.componentName = componentName; + this.componentData = entity.getAttribute(componentName); + } + + execute() { + this.entity.removeAttribute(this.componentName); + Events.emit('componentremove', { + entity: this.entity, + component: this.componentName + }); + } + + undo() { + this.entity.setAttribute(this.componentName, this.componentData); + Events.emit('componentadd', { + entity: this.entity, + component: this.componentName + }); + } +} diff --git a/src/editor/lib/commands/index.js b/src/editor/lib/commands/index.js index 1ce9b2ef8..65add95e8 100644 --- a/src/editor/lib/commands/index.js +++ b/src/editor/lib/commands/index.js @@ -1 +1,3 @@ export { EntityUpdateCommand } from './EntityUpdateCommand.js'; +export { EntityAddComponentCommand } from './EntityAddComponentCommand.js'; +export { EntityRemoveComponentCommand } from './EntityRemoveComponentCommand.js'; From d9cf7f14f537692a7fbd0beb50ee295086f7aed0 Mon Sep 17 00:00:00 2001 From: Vincent Fretin Date: Mon, 26 Aug 2024 10:28:28 +0200 Subject: [PATCH 02/49] add EntityCreateCommand and modify createLayerFunctions to use it --- .../AddLayerPanel/createLayerFunctions.js | 119 ++++++++---------- src/editor/index.js | 6 +- .../lib/commands/EntityCreateCommand.js | 75 +++++++++++ src/editor/lib/commands/index.js | 1 + src/editor/lib/entity.js | 29 ----- 5 files changed, 127 insertions(+), 103 deletions(-) create mode 100644 src/editor/lib/commands/EntityCreateCommand.js diff --git a/src/editor/components/components/AddLayerPanel/createLayerFunctions.js b/src/editor/components/components/AddLayerPanel/createLayerFunctions.js index 4c50c3313..843472485 100644 --- a/src/editor/components/components/AddLayerPanel/createLayerFunctions.js +++ b/src/editor/components/components/AddLayerPanel/createLayerFunctions.js @@ -1,5 +1,6 @@ import Events from '../../../lib/Events'; import { loadScript, roundCoord } from '../../../../../src/utils.js'; +import { EntityCreateCommand } from '../../../lib/commands'; export function createSvgExtrudedEntity() { // This component accepts a svgString and creates a new entity with geometry extruded @@ -15,19 +16,16 @@ export function createSvgExtrudedEntity() { ` ); if (svgString && svgString !== '') { - const newEl = document.createElement('a-entity'); - newEl.setAttribute('svg-extruder', `svgString: ${svgString}`); - newEl.setAttribute('data-layer-name', 'SVG Path • My Custom Path'); - const parentEl = document.querySelector('#street-container'); - newEl.addEventListener( - 'loaded', - () => { - Events.emit('entitycreated', newEl); - AFRAME.INSPECTOR.selectEntity(newEl); - }, - { once: true } + const definition = { + element: 'a-entity', + components: { + 'svg-extruder': `svgString: ${svgString}`, + 'data-layer-name': 'SVG Path • My Custom Path' + } + }; + AFRAME.INSPECTOR.execute( + new EntityCreateCommand(AFRAME.INSPECTOR, definition) ); - parentEl.appendChild(newEl); } } @@ -172,59 +170,44 @@ export function createCustomModel() { 'https://cdn.glitch.global/690c7ea3-3f1c-434b-8b8d-3907b16de83c/Mission_Bay_school_low_poly_model_v03_draco.glb' ); if (modelUrl && modelUrl !== '') { - const newEl = document.createElement('a-entity'); - newEl.classList.add('custom-model'); - newEl.setAttribute('gltf-model', `url(${modelUrl})`); - newEl.setAttribute('data-layer-name', 'glTF Model • My Custom Object'); - const parentEl = document.querySelector('#street-container'); - newEl.addEventListener( - 'loaded', - () => { - Events.emit('entitycreated', newEl); - AFRAME.INSPECTOR.selectEntity(newEl); - }, - { once: true } + const definition = { + class: 'custom-model', + components: { + 'gltf-model': `url(${modelUrl})`, + 'data-layer-name': 'glTF Model • My Custom Object' + } + }; + AFRAME.INSPECTOR.execute( + new EntityCreateCommand(AFRAME.INSPECTOR, definition) ); - parentEl.appendChild(newEl); } } export function createPrimitiveGeometry() { - const newEl = document.createElement('a-entity'); - newEl.setAttribute('geometry', 'primitive: circle; radius: 50;'); - newEl.setAttribute('rotation', '-90 -90 0'); - newEl.setAttribute( - 'data-layer-name', - 'Plane Geometry • Traffic Circle Asphalt' - ); - newEl.setAttribute('material', 'src: #asphalt-texture; repeat: 5 5;'); - const parentEl = document.querySelector('#street-container'); - newEl.addEventListener( - 'loaded', - () => { - Events.emit('entitycreated', newEl); - AFRAME.INSPECTOR.selectEntity(newEl); - }, - { once: true } + const definition = { + 'data-layer-name': 'Plane Geometry • Traffic Circle Asphalt', + components: { + geometry: 'primitive: circle; radius: 50;', + rotation: '-90 -90 0', + material: 'src: #asphalt-texture; repeat: 5 5;' + } + }; + AFRAME.INSPECTOR.execute( + new EntityCreateCommand(AFRAME.INSPECTOR, definition) ); - parentEl.appendChild(newEl); } export function createIntersection() { - const newEl = document.createElement('a-entity'); - newEl.setAttribute('intersection', ''); - newEl.setAttribute('data-layer-name', 'Street • Intersection 90º'); - newEl.setAttribute('rotation', '-90 -90 0'); - const parentEl = document.querySelector('#street-container'); - newEl.addEventListener( - 'loaded', - () => { - Events.emit('entitycreated', newEl); - AFRAME.INSPECTOR.selectEntity(newEl); - }, - { once: true } + const definition = { + 'data-layer-name': 'Street • Intersection 90º', + components: { + intersection: '', + rotation: '-90 -90 0' + } + }; + AFRAME.INSPECTOR.execute( + new EntityCreateCommand(AFRAME.INSPECTOR, definition) ); - parentEl.appendChild(newEl); } export function createSplatObject() { @@ -236,21 +219,17 @@ export function createSplatObject() { ); if (modelUrl && modelUrl !== '') { - const newEl = document.createElement('a-entity'); - newEl.classList.add('splat-model'); - newEl.setAttribute('data-no-pause', ''); - newEl.setAttribute('gaussian_splatting', `src: ${modelUrl}`); - newEl.setAttribute('data-layer-name', 'Splat Model • My Custom Object'); - newEl.play(); - const parentEl = document.querySelector('#street-container'); - newEl.addEventListener( - 'loaded', - () => { - Events.emit('entitycreated', newEl); - AFRAME.INSPECTOR.selectEntity(newEl); - }, - { once: true } + const definition = { + class: 'splat-model', + 'data-layer-name': 'Splat Model • My Custom Object', + 'data-no-pause': '', + components: { + gaussian_splatting: `src: ${modelUrl}` + } + }; + const entity = AFRAME.INSPECTOR.execute( + new EntityCreateCommand(AFRAME.INSPECTOR, definition) ); - parentEl.appendChild(newEl); + entity.play(); } } diff --git a/src/editor/index.js b/src/editor/index.js index 266cdfb85..c8e1263bc 100644 --- a/src/editor/index.js +++ b/src/editor/index.js @@ -5,7 +5,6 @@ import { AuthProvider, GeoProvider } from './contexts'; import Events from './lib/Events'; import { AssetsLoader } from './lib/assetsLoader'; import { initCameras } from './lib/cameras'; -import { createEntity } from './lib/entity'; import { History } from './lib/history'; import { Shortcuts } from './lib/shortcuts'; import { Viewport } from './lib/viewport'; @@ -13,6 +12,7 @@ import { firebaseConfig } from './services/firebase.js'; import './style/index.scss'; import ReactGA from 'react-ga4'; import posthog from 'posthog-js'; +import { EntityCreateCommand } from './lib/commands'; function Inspector() { this.assetsLoader = new AssetsLoader(); @@ -214,9 +214,7 @@ Inspector.prototype = { }); Events.on('entitycreate', (definition) => { - createEntity(definition, (entity) => { - this.selectEntity(entity); - }); + this.execute(new EntityCreateCommand(this, definition)); }); this.sceneEl.addEventListener('newScene', () => { diff --git a/src/editor/lib/commands/EntityCreateCommand.js b/src/editor/lib/commands/EntityCreateCommand.js new file mode 100644 index 000000000..9ee49ac6d --- /dev/null +++ b/src/editor/lib/commands/EntityCreateCommand.js @@ -0,0 +1,75 @@ +import Events from '../Events'; +import { Command } from '../command.js'; + +const NOT_COMPONENTS = ['id', 'class', 'mixin']; + +/** + * Helper function to add a new entity with a list of components + * @param {object} definition Entity definition to add, only components is required: + * {element: 'a-entity', id: "hbiuSdYL2", class: "box", components: {geometry: 'primitive:box'}} + * @return {Element} Entity created + */ +export class EntityCreateCommand extends Command { + constructor(editor, definition) { + super(editor); + + this.type = 'EntityCreateCommand'; + this.name = 'Create Entity'; + this.definition = definition; + this.entity = null; + } + + execute() { + const definition = this.definition; + this.entity = document.createElement(definition.element || 'a-entity'); + const entity = this.entity; + + // Set id + if (definition.id) { + entity.id = definition.id; + } + + // Set class, mixin + for (const attribute of NOT_COMPONENTS) { + if (attribute !== 'id' && definition[attribute]) { + entity.setAttribute(attribute, definition[attribute]); + } + } + + // Set data attributes + for (const key in definition) { + if (key.startsWith('data-')) { + entity.setAttribute(key, definition[key]); + } + } + + // Set components + for (const componentName in definition.components) { + const componentValue = definition.components[componentName]; + entity.setAttribute(componentName, componentValue); + } + + // Emit event after entity is loaded + this.entity.addEventListener( + 'loaded', + () => { + this.editor.selectEntity(this.entity); + Events.emit('entitycreated', this.entity); + }, + { once: true } + ); + + // Add to parentEl if defined of fallback to scene container + const parentEl = + this.definition.parentEl ?? document.querySelector('#street-container'); + parentEl.appendChild(this.entity); + return entity; + } + + undo() { + if (this.entity) { + this.editor.selectEntity(null); + this.entity.parentNode.removeChild(this.entity); + } + } +} diff --git a/src/editor/lib/commands/index.js b/src/editor/lib/commands/index.js index 65add95e8..1a0b61d6c 100644 --- a/src/editor/lib/commands/index.js +++ b/src/editor/lib/commands/index.js @@ -1,3 +1,4 @@ export { EntityUpdateCommand } from './EntityUpdateCommand.js'; export { EntityAddComponentCommand } from './EntityAddComponentCommand.js'; export { EntityRemoveComponentCommand } from './EntityRemoveComponentCommand.js'; +export { EntityCreateCommand } from './EntityCreateCommand.js'; diff --git a/src/editor/lib/entity.js b/src/editor/lib/entity.js index 7c3dff355..8f048d185 100644 --- a/src/editor/lib/entity.js +++ b/src/editor/lib/entity.js @@ -562,32 +562,3 @@ export function printEntity(entity, onDoubleClick) { ); } - -/** - * Helper function to add a new entity with a list of components - * @param {object} definition Entity definition to add: - * {element: 'a-entity', components: {geometry: 'primitive:box'}} - * @return {Element} Entity created - */ -export function createEntity(definition, cb) { - const entity = document.createElement(definition.element); - - // load default attributes - for (let attr in definition.components) { - entity.setAttribute(attr, definition.components[attr]); - } - - // Ensure the components are loaded before update the UI - entity.addEventListener( - 'loaded', - () => { - Events.emit('entitycreated', entity); - cb(entity); - }, - { once: true } - ); - - AFRAME.scenes[0].appendChild(entity); - - return entity; -} From 6835588747a36f09d0471f97eb224de241062653 Mon Sep 17 00:00:00 2001 From: Vincent Fretin Date: Mon, 26 Aug 2024 09:19:00 +0200 Subject: [PATCH 03/49] use correct componentadd or entityupdate commands when changing street-geo --- .../AddLayerPanel/createLayerFunctions.js | 78 ++++++++++++++----- 1 file changed, 57 insertions(+), 21 deletions(-) diff --git a/src/editor/components/components/AddLayerPanel/createLayerFunctions.js b/src/editor/components/components/AddLayerPanel/createLayerFunctions.js index 843472485..5ad7caa79 100644 --- a/src/editor/components/components/AddLayerPanel/createLayerFunctions.js +++ b/src/editor/components/components/AddLayerPanel/createLayerFunctions.js @@ -1,6 +1,10 @@ import Events from '../../../lib/Events'; import { loadScript, roundCoord } from '../../../../../src/utils.js'; -import { EntityCreateCommand } from '../../../lib/commands'; +import { + EntityAddComponentCommand, + EntityCreateCommand, + EntityUpdateCommand +} from '../../../lib/commands'; export function createSvgExtrudedEntity() { // This component accepts a svgString and creates a new entity with geometry extruded @@ -28,7 +32,6 @@ export function createSvgExtrudedEntity() { ); } } - export function createMapbox() { // This component accepts a long / lat and renders a plane with dimensions that // (should be) at a correct scale. @@ -44,13 +47,28 @@ export function createMapbox() { longitude = roundCoord(parseFloat(streetGeo['longitude'])); } - geoLayer.setAttribute( - 'street-geo', - ` - latitude: ${latitude}; longitude: ${longitude}; maps: mapbox2d - ` - ); - Events.emit('entitycreated', geoLayer); + if (streetGeo) { + AFRAME.INSPECTOR.execute( + new EntityUpdateCommand(AFRAME.INSPECTOR, { + entity: geoLayer, + component: 'street-geo', + property: '', + value: { + latitude: latitude, + longitude: longitude, + maps: 'mapbox2d' + } + }) + ); + } else { + AFRAME.INSPECTOR.execute( + new EntityAddComponentCommand(AFRAME.INSPECTOR, geoLayer, 'street-geo', { + latitude: latitude, + longitude: longitude, + maps: 'mapbox2d' + }) + ); + } } export function createStreetmixStreet(position, streetmixURL, hideBuildings) { @@ -78,7 +96,6 @@ export function createStreetmixStreet(position, streetmixURL, hideBuildings) { ); const parentEl = document.querySelector('#street-container'); parentEl.appendChild(newEl); - // update sceneGraph Events.emit('entitycreated', newEl); } } @@ -129,9 +146,7 @@ export function create3DTiles() { let latitude = 0; let longitude = 0; let ellipsoidalHeight = 0; - const streetGeo = document - .getElementById('reference-layers') - ?.getAttribute('street-geo'); + const streetGeo = geoLayer?.getAttribute('street-geo'); if (streetGeo && streetGeo['latitude'] && streetGeo['longitude']) { latitude = roundCoord(parseFloat(streetGeo['latitude'])); @@ -139,14 +154,35 @@ export function create3DTiles() { ellipsoidalHeight = parseFloat(streetGeo['ellipsoidalHeight']) || 0; } - geoLayer.setAttribute( - 'street-geo', - ` - latitude: ${latitude}; longitude: ${longitude}; ellipsoidalHeight: ${ellipsoidalHeight}; maps: google3d - ` - ); - // update sceneGraph - Events.emit('entitycreated', geoLayer); + if (streetGeo) { + AFRAME.INSPECTOR.execute( + new EntityUpdateCommand(AFRAME.INSPECTOR, { + entity: geoLayer, + component: 'street-geo', + property: '', + value: { + latitude: latitude, + longitude: longitude, + ellipsoidalHeight: ellipsoidalHeight, + maps: 'google3d' + } + }) + ); + } else { + AFRAME.INSPECTOR.execute( + new EntityAddComponentCommand( + AFRAME.INSPECTOR, + geoLayer, + 'street-geo', + { + latitude: latitude, + longitude: longitude, + ellipsoidalHeight: ellipsoidalHeight, + maps: 'google3d' + } + ) + ); + } }; if (AFRAME.components['loader-3dtiles']) { From 6127e84aa3c2092f3ab469e40c6ddc9ed91fd8f0 Mon Sep 17 00:00:00 2001 From: Vincent Fretin Date: Mon, 26 Aug 2024 11:10:41 +0200 Subject: [PATCH 04/49] update createStreetmixStreet to use command --- .../AddLayerPanel/createLayerFunctions.js | 29 +++++++++---------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/src/editor/components/components/AddLayerPanel/createLayerFunctions.js b/src/editor/components/components/AddLayerPanel/createLayerFunctions.js index 5ad7caa79..a163b466d 100644 --- a/src/editor/components/components/AddLayerPanel/createLayerFunctions.js +++ b/src/editor/components/components/AddLayerPanel/createLayerFunctions.js @@ -1,4 +1,3 @@ -import Events from '../../../lib/Events'; import { loadScript, roundCoord } from '../../../../../src/utils.js'; import { EntityAddComponentCommand, @@ -80,23 +79,21 @@ export function createStreetmixStreet(position, streetmixURL, hideBuildings) { 'https://streetmix.net/kfarr/3/3dstreet-demo-street' ); } + // position the street further from the current one so as not to overlap each other if (streetmixURL && streetmixURL !== '') { - const newEl = document.createElement('a-entity'); - newEl.setAttribute('id', streetmixURL); - // position the street further from the current one so as not to overlap each other - if (position) { - newEl.setAttribute('position', position); - } else { - newEl.setAttribute('position', '0 0 -20'); - } + const definition = { + id: streetmixURL, + components: { + position: position ?? '0 0 -20', + 'streetmix-loader': { + streetmixStreetURL: streetmixURL, + showBuildings: !hideBuildings + } + } + }; - newEl.setAttribute( - 'streetmix-loader', - `streetmixStreetURL: ${streetmixURL}; showBuildings: ${!hideBuildings}` - ); - const parentEl = document.querySelector('#street-container'); - parentEl.appendChild(newEl); - Events.emit('entitycreated', newEl); + const command = new EntityCreateCommand(AFRAME.INSPECTOR, definition); + AFRAME.INSPECTOR.execute(command); } } From 85eda94f6389cdc2809910975b8e3239c5059aca Mon Sep 17 00:00:00 2001 From: Vincent Fretin Date: Mon, 26 Aug 2024 11:26:03 +0200 Subject: [PATCH 05/49] remove unused entitycreate, replace it by updatescenegraph, remove unused addEntity --- .../components/modals/ScenesModal/ScenesModal.component.jsx | 2 +- src/editor/components/scenegraph/Toolbar.js | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/editor/components/modals/ScenesModal/ScenesModal.component.jsx b/src/editor/components/modals/ScenesModal/ScenesModal.component.jsx index 2f543b391..435b0738c 100644 --- a/src/editor/components/modals/ScenesModal/ScenesModal.component.jsx +++ b/src/editor/components/modals/ScenesModal/ScenesModal.component.jsx @@ -68,7 +68,7 @@ const ScenesModal = ({ isOpen, onClose, initialTab = 'owner', delay }) => { AFRAME.scenes[0].setAttribute('metadata', 'sceneId', sceneId); AFRAME.scenes[0].setAttribute('metadata', 'sceneTitle', sceneTitle); - Events.emit('entitycreate', { element: 'a-entity', components: {} }); + Events.emit('updatescenegraph'); STREET.notify.successMessage('Scene loaded from 3DStreet Cloud.'); onClose(); } diff --git a/src/editor/components/scenegraph/Toolbar.js b/src/editor/components/scenegraph/Toolbar.js index e48865630..f99913099 100644 --- a/src/editor/components/scenegraph/Toolbar.js +++ b/src/editor/components/scenegraph/Toolbar.js @@ -339,10 +339,6 @@ export default class Toolbar extends Component { } } - addEntity() { - Events.emit('entitycreate', { element: 'a-entity', components: {} }); - } - toggleScenePlaying = () => { if (this.state.isPlaying) { AFRAME.scenes[0].pause(); From cfcffc00b44d285769efcad1fb2abbf73dfc1164 Mon Sep 17 00:00:00 2001 From: Vincent Fretin Date: Mon, 26 Aug 2024 11:45:20 +0200 Subject: [PATCH 06/49] modify createEntity and createEntityOnPosition to use EntityCreateCommand --- .../AddLayerPanel/AddLayerPanel.component.jsx | 80 +++++++++---------- 1 file changed, 36 insertions(+), 44 deletions(-) diff --git a/src/editor/components/components/AddLayerPanel/AddLayerPanel.component.jsx b/src/editor/components/components/AddLayerPanel/AddLayerPanel.component.jsx index 6f2796f7d..c7ace1130 100644 --- a/src/editor/components/components/AddLayerPanel/AddLayerPanel.component.jsx +++ b/src/editor/components/components/AddLayerPanel/AddLayerPanel.component.jsx @@ -15,6 +15,7 @@ import Events from '../../../lib/Events'; import pickPointOnGroundPlane from '../../../lib/pick-point-on-ground-plane'; import { layersData, streetLayersData } from './layersData.js'; import { LayersOptions } from './LayersOptions.js'; +import { EntityCreateCommand } from '../../../lib/commands/EntityCreateCommand.js'; // Create an empty image const emptyImg = new Image(); @@ -131,24 +132,13 @@ const createEntityOnPosition = (mixinId, position) => { if (previewEntity) { previewEntity.remove(); } - const newEntity = document.createElement('a-entity'); - newEntity.setAttribute('mixin', mixinId); - newEntity.addEventListener( - 'loaded', - () => { - Events.emit('entitycreated', newEntity); - AFRAME.INSPECTOR.selectEntity(newEntity); - }, - { once: true } - ); - newEntity.setAttribute('position', position); - const streetContainer = document.querySelector('#street-container'); - // apppend element as a child of street-container - if (streetContainer) { - streetContainer.appendChild(newEntity); - } else { - AFRAME.scenes[0].appendChild(newEntity); - } + const command = new EntityCreateCommand(AFRAME.INSPECTOR, { + mixin: mixinId, + components: { + position: position + } + }); + AFRAME.INSPECTOR.execute(command); }; const createEntity = (mixinId) => { @@ -156,16 +146,10 @@ const createEntity = (mixinId) => { if (previewEntity) { previewEntity.remove(); } - const newEntity = document.createElement('a-entity'); - newEntity.setAttribute('mixin', mixinId); - newEntity.addEventListener( - 'loaded', - () => { - Events.emit('entitycreated', newEntity); - AFRAME.INSPECTOR.selectEntity(newEntity); - }, - { once: true } - ); + const newEntityObject = { + mixin: mixinId, + components: {} + }; const selectedElement = AFRAME.INSPECTOR.selectedEntity; const [ancestorEl, inSegment] = selectedElement @@ -176,27 +160,40 @@ const createEntity = (mixinId) => { if (selectedElement && !ancestorEl.parentEl.isScene) { // append element as a child of the entity with .custom-group class. let customGroupEl = ancestorEl.querySelector('.custom-group'); - let entityToMove; + let customGroupCreated = false; if (!customGroupEl) { customGroupEl = document.createElement('a-entity'); // .custom-group entity is a child of segment or .street-parent/.buildings-parent elements ancestorEl.appendChild(customGroupEl); customGroupEl.classList.add('custom-group'); - entityToMove = customGroupEl; - } else { - entityToMove = newEntity; + customGroupCreated = true; } - customGroupEl.appendChild(newEntity); + newEntityObject.parentEl = customGroupEl; if (inSegment) { // get elevation position Y from attribute of segment element const segmentElevationPosY = getSegmentElevationPosY(ancestorEl); // set position y by elevation level of segment - entityToMove.setAttribute('position', { y: segmentElevationPosY }); + if (customGroupCreated) { + customGroupEl.setAttribute('position', { y: segmentElevationPosY }); + newEntityObject.components.position = { x: 0, y: 0, z: 0 }; + } else { + newEntityObject.components.position = { + x: 0, + y: segmentElevationPosY, + z: 0 + }; + } } else { // if we are creating element not inside segment-parent - selectedElement.object3D.getWorldPosition(entityToMove.object3D.position); - entityToMove.object3D.parent.worldToLocal(entityToMove.object3D.position); + const pos = new THREE.Vector3(); + selectedElement.object3D.getWorldPosition(pos); + if (customGroupCreated) { + customGroupEl.object3D.parent.worldToLocal(pos); + } else { + customGroupEl.object3D.worldToLocal(pos); + } + newEntityObject.components.position = { x: pos.x, y: pos.y, z: pos.z }; } } else { const position = pickPointOnGroundPlane({ @@ -204,15 +201,10 @@ const createEntity = (mixinId) => { normalizedY: -0.1, camera: AFRAME.INSPECTOR.camera }); - newEntity.setAttribute('position', position); - const streetContainer = document.querySelector('#street-container'); - // apppend element as a child of street-container - if (streetContainer) { - streetContainer.appendChild(newEntity); - } else { - AFRAME.scenes[0].appendChild(newEntity); - } + newEntityObject.components.position = position; } + const command = new EntityCreateCommand(AFRAME.INSPECTOR, newEntityObject); + AFRAME.INSPECTOR.execute(command); }; const cardMouseEnter = (mixinId) => { From a289282f18db3a882a31ceb57ffb217cba9a9d9c Mon Sep 17 00:00:00 2001 From: Vincent Fretin Date: Mon, 26 Aug 2024 12:09:33 +0200 Subject: [PATCH 07/49] emit entityremoved event when removing entity to update scene graph --- src/editor/components/scenegraph/SceneGraph.js | 2 ++ src/editor/lib/commands/EntityCreateCommand.js | 2 ++ src/editor/lib/entity.js | 1 + 3 files changed, 5 insertions(+) diff --git a/src/editor/components/scenegraph/SceneGraph.js b/src/editor/components/scenegraph/SceneGraph.js index 75355ae9c..d25d9cfef 100644 --- a/src/editor/components/scenegraph/SceneGraph.js +++ b/src/editor/components/scenegraph/SceneGraph.js @@ -45,6 +45,7 @@ export default class SceneGraph extends React.Component { Events.on('entityidchange', this.rebuildEntityOptions); Events.on('entitycreated', this.rebuildEntityOptions); Events.on('entityclone', this.rebuildEntityOptions); + Events.on('entityremoved', this.rebuildEntityOptions); Events.on('entityupdate', this.onMixinUpdate); } @@ -53,6 +54,7 @@ export default class SceneGraph extends React.Component { Events.off('entityidchange', this.rebuildEntityOptions); Events.off('entitycreated', this.rebuildEntityOptions); Events.off('entityclone', this.rebuildEntityOptions); + Events.off('entityremoved', this.rebuildEntityOptions); Events.off('entityupdate', this.onMixinUpdate); } diff --git a/src/editor/lib/commands/EntityCreateCommand.js b/src/editor/lib/commands/EntityCreateCommand.js index 9ee49ac6d..47b9983f3 100644 --- a/src/editor/lib/commands/EntityCreateCommand.js +++ b/src/editor/lib/commands/EntityCreateCommand.js @@ -69,7 +69,9 @@ export class EntityCreateCommand extends Command { undo() { if (this.entity) { this.editor.selectEntity(null); + this.editor.removeObject(this.entity.object3D); this.entity.parentNode.removeChild(this.entity); + Events.emit('entityremoved', this.entity); } } } diff --git a/src/editor/lib/entity.js b/src/editor/lib/entity.js index 8f048d185..400c7f6e8 100644 --- a/src/editor/lib/entity.js +++ b/src/editor/lib/entity.js @@ -48,6 +48,7 @@ export function removeEntity(entity, force) { AFRAME.INSPECTOR.removeObject(entity.object3D); entity.parentNode.removeChild(entity); AFRAME.INSPECTOR.selectEntity(closest); + Events.emit('entityremoved', entity); } } } From cff2b7c208cdc5462d054e446ee5c0421e2ef79a Mon Sep 17 00:00:00 2001 From: Vincent Fretin Date: Mon, 26 Aug 2024 14:10:35 +0200 Subject: [PATCH 08/49] add EntityRemoveCommand --- .../lib/commands/EntityRemoveCommand.js | 52 +++++++++++++++++++ src/editor/lib/commands/index.js | 1 + src/editor/lib/entity.js | 13 ++--- 3 files changed, 58 insertions(+), 8 deletions(-) create mode 100644 src/editor/lib/commands/EntityRemoveCommand.js diff --git a/src/editor/lib/commands/EntityRemoveCommand.js b/src/editor/lib/commands/EntityRemoveCommand.js new file mode 100644 index 000000000..99213c9e0 --- /dev/null +++ b/src/editor/lib/commands/EntityRemoveCommand.js @@ -0,0 +1,52 @@ +import Events from '../Events'; +import { Command } from '../command.js'; +import { findClosestEntity, prepareForSerialization } from '../entity.js'; + +export class EntityRemoveCommand extends Command { + constructor(editor, entity) { + super(editor); + + this.type = 'EntityRemoveCommand'; + this.name = 'Remove Entity'; + this.updatable = false; + + this.entity = entity; + // Store the parent element and index for precise reinsertion + this.parentEl = entity.parentNode; + this.index = Array.from(this.parentEl.children).indexOf(entity); + } + + execute() { + const closest = findClosestEntity(this.entity); + + // Keep a clone not attached to DOM for undo + this.entity.flushToDOM(); + const clone = prepareForSerialization(this.entity); + + // Remove entity + this.editor.removeObject(this.entity.object3D); + this.entity.parentNode.removeChild(this.entity); + Events.emit('entityremoved', this.entity); + + // Replace this.entity by clone + this.entity = clone; + + AFRAME.INSPECTOR.selectEntity(closest); + } + + undo() { + // Reinsert the entity at its original position using the stored index + const referenceNode = this.parentEl.children[this.index] ?? null; + this.parentEl.insertBefore(this.entity, referenceNode); + + // Emit event after entity is loaded + this.entity.addEventListener( + 'loaded', + () => { + this.editor.selectEntity(this.entity); + Events.emit('entitycreated', this.entity); + }, + { once: true } + ); + } +} diff --git a/src/editor/lib/commands/index.js b/src/editor/lib/commands/index.js index 1a0b61d6c..0e5627f43 100644 --- a/src/editor/lib/commands/index.js +++ b/src/editor/lib/commands/index.js @@ -2,3 +2,4 @@ export { EntityUpdateCommand } from './EntityUpdateCommand.js'; export { EntityAddComponentCommand } from './EntityAddComponentCommand.js'; export { EntityRemoveComponentCommand } from './EntityRemoveComponentCommand.js'; export { EntityCreateCommand } from './EntityCreateCommand.js'; +export { EntityRemoveCommand } from './EntityRemoveCommand.js'; diff --git a/src/editor/lib/entity.js b/src/editor/lib/entity.js index 400c7f6e8..e0deba20f 100644 --- a/src/editor/lib/entity.js +++ b/src/editor/lib/entity.js @@ -1,6 +1,6 @@ /* eslint-disable react/no-danger */ import Events from './Events'; -import { EntityUpdateCommand } from './commands'; +import { EntityUpdateCommand, EntityRemoveCommand } from './commands'; import { equal } from './utils'; /** @@ -44,16 +44,13 @@ export function removeEntity(entity, force) { '`?' ) ) { - var closest = findClosestEntity(entity); - AFRAME.INSPECTOR.removeObject(entity.object3D); - entity.parentNode.removeChild(entity); - AFRAME.INSPECTOR.selectEntity(closest); - Events.emit('entityremoved', entity); + const command = new EntityRemoveCommand(AFRAME.INSPECTOR, entity); + AFRAME.INSPECTOR.execute(command); } } } -function findClosestEntity(entity) { +export function findClosestEntity(entity) { // First we try to find the after the entity var nextEntity = entity.nextElementSibling; while (nextEntity && (!nextEntity.isEntity || nextEntity.isInspector)) { @@ -156,7 +153,7 @@ export function getEntityClipboardRepresentation(entity) { * @param {Element} entity Root of the DOM hierarchy. * @return {Element} Copy of the DOM hierarchy ready for serialization. */ -function prepareForSerialization(entity) { +export function prepareForSerialization(entity) { var clone = entity.cloneNode(false); var children = entity.childNodes; for (var i = 0, l = children.length; i < l; i++) { From 5dd1ae8d5a43b6d79dd9898e547b98845b34940d Mon Sep 17 00:00:00 2001 From: Vincent Fretin Date: Mon, 26 Aug 2024 15:01:12 +0200 Subject: [PATCH 09/49] add new createUniqueId function --- package-lock.json | 19 +++++++++++++++++++ package.json | 1 + src/editor/lib/entity.js | 14 ++++++++++++++ 3 files changed, 34 insertions(+) diff --git a/package-lock.json b/package-lock.json index 16ec62b19..b5d0b4e54 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,6 +24,7 @@ "firebase-admin": "^12.1.1", "firebase-functions": "^5.0.1", "lodash-es": "^4.17.21", + "nanoid": "^5.0.7", "posthog-js": "^1.138.3", "prop-types": "^15.8.1", "react": "^18.2.0", @@ -22483,6 +22484,24 @@ "optional": true, "peer": true }, + "node_modules/nanoid": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.0.7.tgz", + "integrity": "sha512-oLxFY2gd2IqnjcYyOXD8XGCftpGtZP2AbHbOkthDkvRywH5ayNtPVy9YlOPcHckXzbLTCHpkb7FB+yuxKV13pQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^18 || >=20" + } + }, "node_modules/natives": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/natives/-/natives-1.1.6.tgz", diff --git a/package.json b/package.json index fe3fbad20..1c6cc7647 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "firebase-admin": "^12.1.1", "firebase-functions": "^5.0.1", "lodash-es": "^4.17.21", + "nanoid": "^5.0.7", "posthog-js": "^1.138.3", "prop-types": "^15.8.1", "react": "^18.2.0", diff --git a/src/editor/lib/entity.js b/src/editor/lib/entity.js index e0deba20f..1ba46afb4 100644 --- a/src/editor/lib/entity.js +++ b/src/editor/lib/entity.js @@ -1,4 +1,5 @@ /* eslint-disable react/no-danger */ +import { nanoid } from 'nanoid'; import Events from './Events'; import { EntityUpdateCommand, EntityRemoveCommand } from './commands'; import { equal } from './utils'; @@ -471,6 +472,19 @@ function getUniqueId(baseId) { return baseId + '-' + i; } +/** + * Create a unique id that can be used on a DOM element. + * @return {string} Valid Id + */ +export function createUniqueId() { + let id = nanoid(); + do { + id = nanoid(); + // be sure to not return an id starting with a number + } while (/^\d/.test(id)); + return id; +} + export function getComponentClipboardRepresentation(entity, componentName) { /** * Get the list of modified properties From 15e361dcb51a7382d7fe5501b9d2a4d47c290509 Mon Sep 17 00:00:00 2001 From: Vincent Fretin Date: Mon, 26 Aug 2024 15:09:50 +0200 Subject: [PATCH 10/49] don't show the random entity id in scene graph, use getEntityDisplayName in delete confirm dialog --- src/editor/lib/entity.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/editor/lib/entity.js b/src/editor/lib/entity.js index 1ba46afb4..bfa0b325a 100644 --- a/src/editor/lib/entity.js +++ b/src/editor/lib/entity.js @@ -41,7 +41,7 @@ export function removeEntity(entity, force) { force === true || confirm( 'Do you really want to remove entity `' + - (entity.id || entity.tagName) + + getEntityDisplayName(entity) + '`?' ) ) { @@ -519,7 +519,7 @@ export function getComponentClipboardRepresentation(entity, componentName) { } export function getEntityDisplayName(entity) { - let entityName = entity.id; + let entityName = ''; if (!entity.isScene && !entityName && entity.getAttribute('class')) { entityName = entity.getAttribute('class').split(' ')[0]; } else if (!entity.isScene && !entityName && entity.getAttribute('mixin')) { From 50348c904b03be057c29748a933932056dede464 Mon Sep 17 00:00:00 2001 From: Vincent Fretin Date: Mon, 26 Aug 2024 15:14:08 +0200 Subject: [PATCH 11/49] get again the entity from id for undo to properly work after undoing remove entity --- src/editor/lib/commands/EntityAddComponentCommand.js | 9 +++++++++ src/editor/lib/commands/EntityRemoveComponentCommand.js | 9 +++++++++ src/editor/lib/commands/EntityUpdateCommand.js | 9 +++++++++ 3 files changed, 27 insertions(+) diff --git a/src/editor/lib/commands/EntityAddComponentCommand.js b/src/editor/lib/commands/EntityAddComponentCommand.js index 7720d17ab..ebad0f274 100644 --- a/src/editor/lib/commands/EntityAddComponentCommand.js +++ b/src/editor/lib/commands/EntityAddComponentCommand.js @@ -1,5 +1,6 @@ import Events from '../Events'; import { Command } from '../command.js'; +import { createUniqueId } from '../entity.js'; export class EntityAddComponentCommand extends Command { constructor(editor, entity, componentName, componentData) { @@ -10,6 +11,9 @@ export class EntityAddComponentCommand extends Command { this.updatable = false; this.entity = entity; + if (!this.entity.id) { + this.entity.id = createUniqueId(); + } this.componentName = componentName; this.componentData = componentData; } @@ -23,6 +27,11 @@ export class EntityAddComponentCommand extends Command { } undo() { + // Get again the entity from id, the entity may have been recreated if it was removed then undone. + const entity = document.getElementById(this.entity.id); + if (this.entity !== entity) { + this.entity = entity; + } this.entity.removeAttribute(this.componentName); Events.emit('componentremove', { entity: this.entity, diff --git a/src/editor/lib/commands/EntityRemoveComponentCommand.js b/src/editor/lib/commands/EntityRemoveComponentCommand.js index d3206c16c..7456fe43f 100644 --- a/src/editor/lib/commands/EntityRemoveComponentCommand.js +++ b/src/editor/lib/commands/EntityRemoveComponentCommand.js @@ -1,5 +1,6 @@ import Events from '../Events'; import { Command } from '../command.js'; +import { createUniqueId } from '../entity.js'; export class EntityRemoveComponentCommand extends Command { constructor(editor, entity, componentName) { @@ -10,6 +11,9 @@ export class EntityRemoveComponentCommand extends Command { this.updatable = false; this.entity = entity; + if (!this.entity.id) { + this.entity.id = createUniqueId(); + } this.componentName = componentName; this.componentData = entity.getAttribute(componentName); } @@ -23,6 +27,11 @@ export class EntityRemoveComponentCommand extends Command { } undo() { + // Get again the entity from id, the entity may have been recreated if it was removed then undone. + const entity = document.getElementById(this.entity.id); + if (this.entity !== entity) { + this.entity = entity; + } this.entity.setAttribute(this.componentName, this.componentData); Events.emit('componentadd', { entity: this.entity, diff --git a/src/editor/lib/commands/EntityUpdateCommand.js b/src/editor/lib/commands/EntityUpdateCommand.js index 0449a4255..a401fc4f6 100644 --- a/src/editor/lib/commands/EntityUpdateCommand.js +++ b/src/editor/lib/commands/EntityUpdateCommand.js @@ -1,5 +1,6 @@ import Events from '../Events'; import { Command } from '../command.js'; +import { createUniqueId } from '../entity.js'; function updateEntity(entity, component, property, value) { if (property) { @@ -35,6 +36,9 @@ export class EntityUpdateCommand extends Command { this.updatable = true; this.entity = payload.entity; + if (!this.entity.id) { + this.entity.id = createUniqueId(); + } this.component = payload.component; this.property = payload.property; @@ -96,6 +100,11 @@ export class EntityUpdateCommand extends Command { } undo() { + // Get again the entity from id, the entity may have been recreated if it was removed then undone. + const entity = document.getElementById(this.entity.id); + if (this.entity !== entity) { + this.entity = entity; + } if ( this.editor.selectedEntity && this.editor.selectedEntity !== this.entity From 0384ac9ba8e9f555c088106a6536615313ea6bbf Mon Sep 17 00:00:00 2001 From: Vincent Fretin Date: Mon, 26 Aug 2024 15:15:38 +0200 Subject: [PATCH 12/49] always set an id for created entity --- src/editor/lib/commands/EntityCreateCommand.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/editor/lib/commands/EntityCreateCommand.js b/src/editor/lib/commands/EntityCreateCommand.js index 47b9983f3..7156d9222 100644 --- a/src/editor/lib/commands/EntityCreateCommand.js +++ b/src/editor/lib/commands/EntityCreateCommand.js @@ -1,5 +1,6 @@ import Events from '../Events'; import { Command } from '../command.js'; +import { createUniqueId } from '../entity.js'; const NOT_COMPONENTS = ['id', 'class', 'mixin']; @@ -27,6 +28,8 @@ export class EntityCreateCommand extends Command { // Set id if (definition.id) { entity.id = definition.id; + } else { + this.entity.id = createUniqueId(); } // Set class, mixin From f39339f061e27fa4bdee124815a0cf99ffb02016 Mon Sep 17 00:00:00 2001 From: Vincent Fretin Date: Tue, 27 Aug 2024 13:34:30 +0200 Subject: [PATCH 13/49] Remove unused m shortcut that was used in the removed motion capture feature --- src/editor/lib/shortcuts.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/editor/lib/shortcuts.js b/src/editor/lib/shortcuts.js index 69c7b1d4c..41ed3fb7f 100644 --- a/src/editor/lib/shortcuts.js +++ b/src/editor/lib/shortcuts.js @@ -65,11 +65,6 @@ export const Shortcuts = { Events.emit('togglegrid'); } - // m: motion capture - if (keyCode === 77) { - Events.emit('togglemotioncapture'); - } - // backspace & supr: remove selected entity if (keyCode === 8 || keyCode === 46) { removeSelectedEntity(); From c6786b1aa4dfb85dfb1b169c4ba15a264eed2353 Mon Sep 17 00:00:00 2001 From: Vincent Fretin Date: Tue, 27 Aug 2024 16:11:51 +0200 Subject: [PATCH 14/49] rename EntityAddComponentCommand to ComponentAddCommand, and EntityRemoveComponentCommand to ComponentRemoveCommand --- .../components/components/AddComponent.js | 4 ++-- .../AddLayerPanel/createLayerFunctions.js | 21 +++++++------------ src/editor/components/components/Component.js | 4 ++-- ...onentCommand.js => ComponentAddCommand.js} | 6 +++--- ...ntCommand.js => ComponentRemoveCommand.js} | 6 +++--- .../lib/commands/EntityCreateCommand.js | 2 +- .../lib/commands/EntityRemoveCommand.js | 2 +- .../lib/commands/EntityUpdateCommand.js | 2 +- src/editor/lib/commands/index.js | 4 ++-- 9 files changed, 23 insertions(+), 28 deletions(-) rename src/editor/lib/commands/{EntityAddComponentCommand.js => ComponentAddCommand.js} (88%) rename src/editor/lib/commands/{EntityRemoveComponentCommand.js => ComponentRemoveCommand.js} (88%) diff --git a/src/editor/components/components/AddComponent.js b/src/editor/components/components/AddComponent.js index e451ac0eb..0180c0419 100644 --- a/src/editor/components/components/AddComponent.js +++ b/src/editor/components/components/AddComponent.js @@ -2,7 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import Select from 'react-select'; import { sendMetric } from '../../services/ga'; -import { EntityAddComponentCommand } from '../../lib/commands'; +import { ComponentAddCommand } from '../../lib/commands'; export default class AddComponent extends React.Component { static propTypes = { @@ -25,7 +25,7 @@ export default class AddComponent extends React.Component { componentName = id ? `${componentName}__${id}` : componentName; } - const command = new EntityAddComponentCommand( + const command = new ComponentAddCommand( AFRAME.INSPECTOR, entity, componentName, diff --git a/src/editor/components/components/AddLayerPanel/createLayerFunctions.js b/src/editor/components/components/AddLayerPanel/createLayerFunctions.js index a163b466d..8118e9a2c 100644 --- a/src/editor/components/components/AddLayerPanel/createLayerFunctions.js +++ b/src/editor/components/components/AddLayerPanel/createLayerFunctions.js @@ -1,6 +1,6 @@ import { loadScript, roundCoord } from '../../../../../src/utils.js'; import { - EntityAddComponentCommand, + ComponentAddCommand, EntityCreateCommand, EntityUpdateCommand } from '../../../lib/commands'; @@ -61,7 +61,7 @@ export function createMapbox() { ); } else { AFRAME.INSPECTOR.execute( - new EntityAddComponentCommand(AFRAME.INSPECTOR, geoLayer, 'street-geo', { + new ComponentAddCommand(AFRAME.INSPECTOR, geoLayer, 'street-geo', { latitude: latitude, longitude: longitude, maps: 'mapbox2d' @@ -167,17 +167,12 @@ export function create3DTiles() { ); } else { AFRAME.INSPECTOR.execute( - new EntityAddComponentCommand( - AFRAME.INSPECTOR, - geoLayer, - 'street-geo', - { - latitude: latitude, - longitude: longitude, - ellipsoidalHeight: ellipsoidalHeight, - maps: 'google3d' - } - ) + new ComponentAddCommand(AFRAME.INSPECTOR, geoLayer, 'street-geo', { + latitude: latitude, + longitude: longitude, + ellipsoidalHeight: ellipsoidalHeight, + maps: 'google3d' + }) ); } }; diff --git a/src/editor/components/components/Component.js b/src/editor/components/components/Component.js index ec19dac3e..b0dcadc53 100644 --- a/src/editor/components/components/Component.js +++ b/src/editor/components/components/Component.js @@ -6,7 +6,7 @@ import PropertyRow from './PropertyRow'; import React from 'react'; import { getComponentClipboardRepresentation } from '../../lib/entity'; import { sendMetric } from '../../services/ga'; -import { EntityRemoveComponentCommand } from '../../lib/commands'; +import { ComponentRemoveCommand } from '../../lib/commands'; const isSingleProperty = AFRAME.schema.isSingleProperty; @@ -82,7 +82,7 @@ export default class Component extends React.Component { if ( confirm('Do you really want to remove component `' + componentName + '`?') ) { - const command = new EntityRemoveComponentCommand( + const command = new ComponentRemoveCommand( AFRAME.INSPECTOR, this.props.entity, componentName diff --git a/src/editor/lib/commands/EntityAddComponentCommand.js b/src/editor/lib/commands/ComponentAddCommand.js similarity index 88% rename from src/editor/lib/commands/EntityAddComponentCommand.js rename to src/editor/lib/commands/ComponentAddCommand.js index ebad0f274..2231f3aae 100644 --- a/src/editor/lib/commands/EntityAddComponentCommand.js +++ b/src/editor/lib/commands/ComponentAddCommand.js @@ -1,12 +1,12 @@ -import Events from '../Events'; +import Events from '../Events.js'; import { Command } from '../command.js'; import { createUniqueId } from '../entity.js'; -export class EntityAddComponentCommand extends Command { +export class ComponentAddCommand extends Command { constructor(editor, entity, componentName, componentData) { super(editor); - this.type = 'EntityAddComponentCommand'; + this.type = 'componentadd'; this.name = 'Add Component'; this.updatable = false; diff --git a/src/editor/lib/commands/EntityRemoveComponentCommand.js b/src/editor/lib/commands/ComponentRemoveCommand.js similarity index 88% rename from src/editor/lib/commands/EntityRemoveComponentCommand.js rename to src/editor/lib/commands/ComponentRemoveCommand.js index 7456fe43f..4326751ce 100644 --- a/src/editor/lib/commands/EntityRemoveComponentCommand.js +++ b/src/editor/lib/commands/ComponentRemoveCommand.js @@ -1,12 +1,12 @@ -import Events from '../Events'; +import Events from '../Events.js'; import { Command } from '../command.js'; import { createUniqueId } from '../entity.js'; -export class EntityRemoveComponentCommand extends Command { +export class ComponentRemoveCommand extends Command { constructor(editor, entity, componentName) { super(editor); - this.type = 'EntityRemoveComponentCommand'; + this.type = 'componentremove'; this.name = 'Remove Component'; this.updatable = false; diff --git a/src/editor/lib/commands/EntityCreateCommand.js b/src/editor/lib/commands/EntityCreateCommand.js index 7156d9222..7a00e7119 100644 --- a/src/editor/lib/commands/EntityCreateCommand.js +++ b/src/editor/lib/commands/EntityCreateCommand.js @@ -14,7 +14,7 @@ export class EntityCreateCommand extends Command { constructor(editor, definition) { super(editor); - this.type = 'EntityCreateCommand'; + this.type = 'entitycreate'; this.name = 'Create Entity'; this.definition = definition; this.entity = null; diff --git a/src/editor/lib/commands/EntityRemoveCommand.js b/src/editor/lib/commands/EntityRemoveCommand.js index 99213c9e0..de0635728 100644 --- a/src/editor/lib/commands/EntityRemoveCommand.js +++ b/src/editor/lib/commands/EntityRemoveCommand.js @@ -6,7 +6,7 @@ export class EntityRemoveCommand extends Command { constructor(editor, entity) { super(editor); - this.type = 'EntityRemoveCommand'; + this.type = 'entityremove'; this.name = 'Remove Entity'; this.updatable = false; diff --git a/src/editor/lib/commands/EntityUpdateCommand.js b/src/editor/lib/commands/EntityUpdateCommand.js index a401fc4f6..617a9e79f 100644 --- a/src/editor/lib/commands/EntityUpdateCommand.js +++ b/src/editor/lib/commands/EntityUpdateCommand.js @@ -31,7 +31,7 @@ export class EntityUpdateCommand extends Command { constructor(editor, payload) { super(editor); - this.type = 'EntityUpdateCommand'; + this.type = 'entityupdate'; this.name = 'Update Entity'; this.updatable = true; diff --git a/src/editor/lib/commands/index.js b/src/editor/lib/commands/index.js index 0e5627f43..a0ec80d3b 100644 --- a/src/editor/lib/commands/index.js +++ b/src/editor/lib/commands/index.js @@ -1,5 +1,5 @@ export { EntityUpdateCommand } from './EntityUpdateCommand.js'; -export { EntityAddComponentCommand } from './EntityAddComponentCommand.js'; -export { EntityRemoveComponentCommand } from './EntityRemoveComponentCommand.js'; +export { ComponentAddCommand } from './ComponentAddCommand.js'; +export { ComponentRemoveCommand } from './ComponentRemoveCommand.js'; export { EntityCreateCommand } from './EntityCreateCommand.js'; export { EntityRemoveCommand } from './EntityRemoveCommand.js'; From 20ae117720a9b3bc79401d1459381b8cdbed98f6 Mon Sep 17 00:00:00 2001 From: Vincent Fretin Date: Tue, 27 Aug 2024 16:45:14 +0200 Subject: [PATCH 15/49] use a single payload param for ComponentAddCommand/ComponentRemoveCommand similar to EntityUpdateCommand --- .../components/components/AddComponent.js | 9 +++---- .../AddLayerPanel/createLayerFunctions.js | 26 ++++++++++++------- src/editor/components/components/Component.js | 9 +++---- .../lib/commands/ComponentAddCommand.js | 17 ++++++------ .../lib/commands/ComponentRemoveCommand.js | 17 ++++++------ 5 files changed, 43 insertions(+), 35 deletions(-) diff --git a/src/editor/components/components/AddComponent.js b/src/editor/components/components/AddComponent.js index 0180c0419..36ae9a78d 100644 --- a/src/editor/components/components/AddComponent.js +++ b/src/editor/components/components/AddComponent.js @@ -25,12 +25,11 @@ export default class AddComponent extends React.Component { componentName = id ? `${componentName}__${id}` : componentName; } - const command = new ComponentAddCommand( - AFRAME.INSPECTOR, + const command = new ComponentAddCommand(AFRAME.INSPECTOR, { entity, - componentName, - '' - ); + component: componentName, + value: '' + }); AFRAME.INSPECTOR.execute(command); sendMetric('Components', 'addComponent', componentName); diff --git a/src/editor/components/components/AddLayerPanel/createLayerFunctions.js b/src/editor/components/components/AddLayerPanel/createLayerFunctions.js index 8118e9a2c..5cfe61411 100644 --- a/src/editor/components/components/AddLayerPanel/createLayerFunctions.js +++ b/src/editor/components/components/AddLayerPanel/createLayerFunctions.js @@ -61,10 +61,14 @@ export function createMapbox() { ); } else { AFRAME.INSPECTOR.execute( - new ComponentAddCommand(AFRAME.INSPECTOR, geoLayer, 'street-geo', { - latitude: latitude, - longitude: longitude, - maps: 'mapbox2d' + new ComponentAddCommand(AFRAME.INSPECTOR, { + entity: geoLayer, + component: 'street-geo', + value: { + latitude: latitude, + longitude: longitude, + maps: 'mapbox2d' + } }) ); } @@ -167,11 +171,15 @@ export function create3DTiles() { ); } else { AFRAME.INSPECTOR.execute( - new ComponentAddCommand(AFRAME.INSPECTOR, geoLayer, 'street-geo', { - latitude: latitude, - longitude: longitude, - ellipsoidalHeight: ellipsoidalHeight, - maps: 'google3d' + new ComponentAddCommand(AFRAME.INSPECTOR, { + entity: geoLayer, + component: 'street-geo', + value: { + latitude: latitude, + longitude: longitude, + ellipsoidalHeight: ellipsoidalHeight, + maps: 'google3d' + } }) ); } diff --git a/src/editor/components/components/Component.js b/src/editor/components/components/Component.js index b0dcadc53..d32926a4e 100644 --- a/src/editor/components/components/Component.js +++ b/src/editor/components/components/Component.js @@ -82,11 +82,10 @@ export default class Component extends React.Component { if ( confirm('Do you really want to remove component `' + componentName + '`?') ) { - const command = new ComponentRemoveCommand( - AFRAME.INSPECTOR, - this.props.entity, - componentName - ); + const command = new ComponentRemoveCommand(AFRAME.INSPECTOR, { + entity: this.props.entity, + component: componentName + }); AFRAME.INSPECTOR.execute(command); sendMetric('Components', 'removeComponent', componentName); } diff --git a/src/editor/lib/commands/ComponentAddCommand.js b/src/editor/lib/commands/ComponentAddCommand.js index 2231f3aae..1803164e5 100644 --- a/src/editor/lib/commands/ComponentAddCommand.js +++ b/src/editor/lib/commands/ComponentAddCommand.js @@ -3,26 +3,27 @@ import { Command } from '../command.js'; import { createUniqueId } from '../entity.js'; export class ComponentAddCommand extends Command { - constructor(editor, entity, componentName, componentData) { + constructor(editor, payload) { super(editor); this.type = 'componentadd'; this.name = 'Add Component'; this.updatable = false; - this.entity = entity; + this.entity = payload.entity; if (!this.entity.id) { this.entity.id = createUniqueId(); } - this.componentName = componentName; - this.componentData = componentData; + this.component = payload.component; + this.value = payload.value; } execute() { - this.entity.setAttribute(this.componentName, this.componentData); + this.entity.setAttribute(this.component, this.value); Events.emit('componentadd', { entity: this.entity, - component: this.componentName + component: this.component, + value: this.value }); } @@ -32,10 +33,10 @@ export class ComponentAddCommand extends Command { if (this.entity !== entity) { this.entity = entity; } - this.entity.removeAttribute(this.componentName); + this.entity.removeAttribute(this.component); Events.emit('componentremove', { entity: this.entity, - component: this.componentName + component: this.component }); } } diff --git a/src/editor/lib/commands/ComponentRemoveCommand.js b/src/editor/lib/commands/ComponentRemoveCommand.js index 4326751ce..cc5fd4570 100644 --- a/src/editor/lib/commands/ComponentRemoveCommand.js +++ b/src/editor/lib/commands/ComponentRemoveCommand.js @@ -3,26 +3,26 @@ import { Command } from '../command.js'; import { createUniqueId } from '../entity.js'; export class ComponentRemoveCommand extends Command { - constructor(editor, entity, componentName) { + constructor(editor, payload) { super(editor); this.type = 'componentremove'; this.name = 'Remove Component'; this.updatable = false; - this.entity = entity; + this.entity = payload.entity; if (!this.entity.id) { this.entity.id = createUniqueId(); } - this.componentName = componentName; - this.componentData = entity.getAttribute(componentName); + this.component = payload.component; + this.value = this.entity.getAttribute(this.component); } execute() { - this.entity.removeAttribute(this.componentName); + this.entity.removeAttribute(this.component); Events.emit('componentremove', { entity: this.entity, - component: this.componentName + component: this.component }); } @@ -32,10 +32,11 @@ export class ComponentRemoveCommand extends Command { if (this.entity !== entity) { this.entity = entity; } - this.entity.setAttribute(this.componentName, this.componentData); + this.entity.setAttribute(this.component, this.value); Events.emit('componentadd', { entity: this.entity, - component: this.componentName + component: this.component, + value: this.value }); } } From 5667cc7a67c3b2fb13f6b368e0a44f05a4bb8976 Mon Sep 17 00:00:00 2001 From: Vincent Fretin Date: Tue, 27 Aug 2024 16:47:14 +0200 Subject: [PATCH 16/49] free reference to removed entity --- src/editor/lib/commands/EntityCreateCommand.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/editor/lib/commands/EntityCreateCommand.js b/src/editor/lib/commands/EntityCreateCommand.js index 7a00e7119..da1b42169 100644 --- a/src/editor/lib/commands/EntityCreateCommand.js +++ b/src/editor/lib/commands/EntityCreateCommand.js @@ -75,6 +75,7 @@ export class EntityCreateCommand extends Command { this.editor.removeObject(this.entity.object3D); this.entity.parentNode.removeChild(this.entity); Events.emit('entityremoved', this.entity); + this.entity = null; } } } From 9a1ff8b2ce1033d91a7dc41e2253786243c146ad Mon Sep 17 00:00:00 2001 From: Vincent Fretin Date: Tue, 27 Aug 2024 17:20:15 +0200 Subject: [PATCH 17/49] simplify command execution --- .../components/components/AddComponent.js | 4 +- .../AddLayerPanel/AddLayerPanel.component.jsx | 7 +- .../AddLayerPanel/createLayerFunctions.js | 116 +++++++----------- src/editor/components/components/Component.js | 4 +- src/editor/index.js | 16 ++- .../lib/commands/ComponentAddCommand.js | 2 +- .../lib/commands/ComponentRemoveCommand.js | 2 +- .../lib/commands/EntityCreateCommand.js | 2 +- .../lib/commands/EntityRemoveCommand.js | 2 +- .../lib/commands/EntityUpdateCommand.js | 2 +- src/editor/lib/commands/index.js | 17 ++- src/editor/lib/entity.js | 18 ++- src/editor/lib/viewport.js | 15 +-- 13 files changed, 92 insertions(+), 115 deletions(-) diff --git a/src/editor/components/components/AddComponent.js b/src/editor/components/components/AddComponent.js index 36ae9a78d..465699ed1 100644 --- a/src/editor/components/components/AddComponent.js +++ b/src/editor/components/components/AddComponent.js @@ -2,7 +2,6 @@ import React from 'react'; import PropTypes from 'prop-types'; import Select from 'react-select'; import { sendMetric } from '../../services/ga'; -import { ComponentAddCommand } from '../../lib/commands'; export default class AddComponent extends React.Component { static propTypes = { @@ -25,12 +24,11 @@ export default class AddComponent extends React.Component { componentName = id ? `${componentName}__${id}` : componentName; } - const command = new ComponentAddCommand(AFRAME.INSPECTOR, { + AFRAME.INSPECTOR.execute('componentadd', { entity, component: componentName, value: '' }); - AFRAME.INSPECTOR.execute(command); sendMetric('Components', 'addComponent', componentName); }; diff --git a/src/editor/components/components/AddLayerPanel/AddLayerPanel.component.jsx b/src/editor/components/components/AddLayerPanel/AddLayerPanel.component.jsx index c7ace1130..bf772b609 100644 --- a/src/editor/components/components/AddLayerPanel/AddLayerPanel.component.jsx +++ b/src/editor/components/components/AddLayerPanel/AddLayerPanel.component.jsx @@ -15,7 +15,6 @@ import Events from '../../../lib/Events'; import pickPointOnGroundPlane from '../../../lib/pick-point-on-ground-plane'; import { layersData, streetLayersData } from './layersData.js'; import { LayersOptions } from './LayersOptions.js'; -import { EntityCreateCommand } from '../../../lib/commands/EntityCreateCommand.js'; // Create an empty image const emptyImg = new Image(); @@ -132,13 +131,12 @@ const createEntityOnPosition = (mixinId, position) => { if (previewEntity) { previewEntity.remove(); } - const command = new EntityCreateCommand(AFRAME.INSPECTOR, { + AFRAME.INSPECTOR.execute('entitycreate', { mixin: mixinId, components: { position: position } }); - AFRAME.INSPECTOR.execute(command); }; const createEntity = (mixinId) => { @@ -203,8 +201,7 @@ const createEntity = (mixinId) => { }); newEntityObject.components.position = position; } - const command = new EntityCreateCommand(AFRAME.INSPECTOR, newEntityObject); - AFRAME.INSPECTOR.execute(command); + AFRAME.INSPECTOR.execute('entitycreate', newEntityObject); }; const cardMouseEnter = (mixinId) => { diff --git a/src/editor/components/components/AddLayerPanel/createLayerFunctions.js b/src/editor/components/components/AddLayerPanel/createLayerFunctions.js index 5cfe61411..cced04707 100644 --- a/src/editor/components/components/AddLayerPanel/createLayerFunctions.js +++ b/src/editor/components/components/AddLayerPanel/createLayerFunctions.js @@ -1,9 +1,4 @@ import { loadScript, roundCoord } from '../../../../../src/utils.js'; -import { - ComponentAddCommand, - EntityCreateCommand, - EntityUpdateCommand -} from '../../../lib/commands'; export function createSvgExtrudedEntity() { // This component accepts a svgString and creates a new entity with geometry extruded @@ -26,9 +21,7 @@ export function createSvgExtrudedEntity() { 'data-layer-name': 'SVG Path • My Custom Path' } }; - AFRAME.INSPECTOR.execute( - new EntityCreateCommand(AFRAME.INSPECTOR, definition) - ); + AFRAME.INSPECTOR.execute('entitycreate', definition); } } export function createMapbox() { @@ -47,30 +40,26 @@ export function createMapbox() { } if (streetGeo) { - AFRAME.INSPECTOR.execute( - new EntityUpdateCommand(AFRAME.INSPECTOR, { - entity: geoLayer, - component: 'street-geo', - property: '', - value: { - latitude: latitude, - longitude: longitude, - maps: 'mapbox2d' - } - }) - ); + AFRAME.INSPECTOR.execute('entityupdate', { + entity: geoLayer, + component: 'street-geo', + property: '', + value: { + latitude: latitude, + longitude: longitude, + maps: 'mapbox2d' + } + }); } else { - AFRAME.INSPECTOR.execute( - new ComponentAddCommand(AFRAME.INSPECTOR, { - entity: geoLayer, - component: 'street-geo', - value: { - latitude: latitude, - longitude: longitude, - maps: 'mapbox2d' - } - }) - ); + AFRAME.INSPECTOR.execute('componentadd', { + entity: geoLayer, + component: 'street-geo', + value: { + latitude: latitude, + longitude: longitude, + maps: 'mapbox2d' + } + }); } } @@ -96,8 +85,7 @@ export function createStreetmixStreet(position, streetmixURL, hideBuildings) { } }; - const command = new EntityCreateCommand(AFRAME.INSPECTOR, definition); - AFRAME.INSPECTOR.execute(command); + AFRAME.INSPECTOR.execute('entitycreate', definition); } } @@ -156,32 +144,28 @@ export function create3DTiles() { } if (streetGeo) { - AFRAME.INSPECTOR.execute( - new EntityUpdateCommand(AFRAME.INSPECTOR, { - entity: geoLayer, - component: 'street-geo', - property: '', - value: { - latitude: latitude, - longitude: longitude, - ellipsoidalHeight: ellipsoidalHeight, - maps: 'google3d' - } - }) - ); + AFRAME.INSPECTOR.execute('entityupdate', { + entity: geoLayer, + component: 'street-geo', + property: '', + value: { + latitude: latitude, + longitude: longitude, + ellipsoidalHeight: ellipsoidalHeight, + maps: 'google3d' + } + }); } else { - AFRAME.INSPECTOR.execute( - new ComponentAddCommand(AFRAME.INSPECTOR, { - entity: geoLayer, - component: 'street-geo', - value: { - latitude: latitude, - longitude: longitude, - ellipsoidalHeight: ellipsoidalHeight, - maps: 'google3d' - } - }) - ); + AFRAME.INSPECTOR.execute('componentadd', { + entity: geoLayer, + component: 'street-geo', + value: { + latitude: latitude, + longitude: longitude, + ellipsoidalHeight: ellipsoidalHeight, + maps: 'google3d' + } + }); } }; @@ -213,9 +197,7 @@ export function createCustomModel() { 'data-layer-name': 'glTF Model • My Custom Object' } }; - AFRAME.INSPECTOR.execute( - new EntityCreateCommand(AFRAME.INSPECTOR, definition) - ); + AFRAME.INSPECTOR.execute('entitycreate', definition); } } @@ -228,9 +210,7 @@ export function createPrimitiveGeometry() { material: 'src: #asphalt-texture; repeat: 5 5;' } }; - AFRAME.INSPECTOR.execute( - new EntityCreateCommand(AFRAME.INSPECTOR, definition) - ); + AFRAME.INSPECTOR.execute('entitycreate', definition); } export function createIntersection() { @@ -241,9 +221,7 @@ export function createIntersection() { rotation: '-90 -90 0' } }; - AFRAME.INSPECTOR.execute( - new EntityCreateCommand(AFRAME.INSPECTOR, definition) - ); + AFRAME.INSPECTOR.execute('entitycreate', definition); } export function createSplatObject() { @@ -263,9 +241,7 @@ export function createSplatObject() { gaussian_splatting: `src: ${modelUrl}` } }; - const entity = AFRAME.INSPECTOR.execute( - new EntityCreateCommand(AFRAME.INSPECTOR, definition) - ); + const entity = AFRAME.INSPECTOR.execute('entitycreate', definition); entity.play(); } } diff --git a/src/editor/components/components/Component.js b/src/editor/components/components/Component.js index d32926a4e..e919a7243 100644 --- a/src/editor/components/components/Component.js +++ b/src/editor/components/components/Component.js @@ -6,7 +6,6 @@ import PropertyRow from './PropertyRow'; import React from 'react'; import { getComponentClipboardRepresentation } from '../../lib/entity'; import { sendMetric } from '../../services/ga'; -import { ComponentRemoveCommand } from '../../lib/commands'; const isSingleProperty = AFRAME.schema.isSingleProperty; @@ -82,11 +81,10 @@ export default class Component extends React.Component { if ( confirm('Do you really want to remove component `' + componentName + '`?') ) { - const command = new ComponentRemoveCommand(AFRAME.INSPECTOR, { + AFRAME.INSPECTOR.execute('componentremove', { entity: this.props.entity, component: componentName }); - AFRAME.INSPECTOR.execute(command); sendMetric('Components', 'removeComponent', componentName); } }; diff --git a/src/editor/index.js b/src/editor/index.js index c8e1263bc..b339c1a11 100644 --- a/src/editor/index.js +++ b/src/editor/index.js @@ -12,7 +12,7 @@ import { firebaseConfig } from './services/firebase.js'; import './style/index.scss'; import ReactGA from 'react-ga4'; import posthog from 'posthog-js'; -import { EntityCreateCommand } from './lib/commands'; +import { commandsByType } from './lib/commands/index.js'; function Inspector() { this.assetsLoader = new AssetsLoader(); @@ -214,7 +214,10 @@ Inspector.prototype = { }); Events.on('entitycreate', (definition) => { - this.execute(new EntityCreateCommand(this, definition)); + console.warn( + 'You should call AFRAME.INSPECTOR.execute("entitycreate", definition) instead of Events.emit("entitycreate", definition)' + ); + this.execute('entitycreate', definition); }); this.sceneEl.addEventListener('newScene', () => { @@ -227,8 +230,13 @@ Inspector.prototype = { }); }, - execute: function (cmd, optionalName) { - this.history.execute(cmd, optionalName); + execute: function (cmdName, payload, optionalName) { + const Cmd = commandsByType.get(cmdName); + if (!Cmd) { + console.error(`Command ${cmdName} not found`); + return; + } + this.history.execute(new Cmd(this, payload), optionalName); }, undo: function () { diff --git a/src/editor/lib/commands/ComponentAddCommand.js b/src/editor/lib/commands/ComponentAddCommand.js index 1803164e5..1c851b6f6 100644 --- a/src/editor/lib/commands/ComponentAddCommand.js +++ b/src/editor/lib/commands/ComponentAddCommand.js @@ -3,10 +3,10 @@ import { Command } from '../command.js'; import { createUniqueId } from '../entity.js'; export class ComponentAddCommand extends Command { + static type = 'componentadd'; constructor(editor, payload) { super(editor); - this.type = 'componentadd'; this.name = 'Add Component'; this.updatable = false; diff --git a/src/editor/lib/commands/ComponentRemoveCommand.js b/src/editor/lib/commands/ComponentRemoveCommand.js index cc5fd4570..1d8f2c441 100644 --- a/src/editor/lib/commands/ComponentRemoveCommand.js +++ b/src/editor/lib/commands/ComponentRemoveCommand.js @@ -3,10 +3,10 @@ import { Command } from '../command.js'; import { createUniqueId } from '../entity.js'; export class ComponentRemoveCommand extends Command { + static type = 'componentremove'; constructor(editor, payload) { super(editor); - this.type = 'componentremove'; this.name = 'Remove Component'; this.updatable = false; diff --git a/src/editor/lib/commands/EntityCreateCommand.js b/src/editor/lib/commands/EntityCreateCommand.js index da1b42169..6d6dde4a4 100644 --- a/src/editor/lib/commands/EntityCreateCommand.js +++ b/src/editor/lib/commands/EntityCreateCommand.js @@ -11,10 +11,10 @@ const NOT_COMPONENTS = ['id', 'class', 'mixin']; * @return {Element} Entity created */ export class EntityCreateCommand extends Command { + static type = 'entitycreate'; constructor(editor, definition) { super(editor); - this.type = 'entitycreate'; this.name = 'Create Entity'; this.definition = definition; this.entity = null; diff --git a/src/editor/lib/commands/EntityRemoveCommand.js b/src/editor/lib/commands/EntityRemoveCommand.js index de0635728..47baa6e27 100644 --- a/src/editor/lib/commands/EntityRemoveCommand.js +++ b/src/editor/lib/commands/EntityRemoveCommand.js @@ -3,10 +3,10 @@ import { Command } from '../command.js'; import { findClosestEntity, prepareForSerialization } from '../entity.js'; export class EntityRemoveCommand extends Command { + static type = 'entityremove'; constructor(editor, entity) { super(editor); - this.type = 'entityremove'; this.name = 'Remove Entity'; this.updatable = false; diff --git a/src/editor/lib/commands/EntityUpdateCommand.js b/src/editor/lib/commands/EntityUpdateCommand.js index 617a9e79f..2afffe9c1 100644 --- a/src/editor/lib/commands/EntityUpdateCommand.js +++ b/src/editor/lib/commands/EntityUpdateCommand.js @@ -28,10 +28,10 @@ function updateEntity(entity, component, property, value) { * @constructor */ export class EntityUpdateCommand extends Command { + static type = 'entityupdate'; constructor(editor, payload) { super(editor); - this.type = 'entityupdate'; this.name = 'Update Entity'; this.updatable = true; diff --git a/src/editor/lib/commands/index.js b/src/editor/lib/commands/index.js index a0ec80d3b..492bd58a0 100644 --- a/src/editor/lib/commands/index.js +++ b/src/editor/lib/commands/index.js @@ -1,5 +1,12 @@ -export { EntityUpdateCommand } from './EntityUpdateCommand.js'; -export { ComponentAddCommand } from './ComponentAddCommand.js'; -export { ComponentRemoveCommand } from './ComponentRemoveCommand.js'; -export { EntityCreateCommand } from './EntityCreateCommand.js'; -export { EntityRemoveCommand } from './EntityRemoveCommand.js'; +import { EntityUpdateCommand } from './EntityUpdateCommand.js'; +import { ComponentAddCommand } from './ComponentAddCommand.js'; +import { ComponentRemoveCommand } from './ComponentRemoveCommand.js'; +import { EntityCreateCommand } from './EntityCreateCommand.js'; +import { EntityRemoveCommand } from './EntityRemoveCommand.js'; + +export const commandsByType = new Map(); +commandsByType.set(EntityUpdateCommand.type, EntityUpdateCommand); +commandsByType.set(ComponentAddCommand.type, ComponentAddCommand); +commandsByType.set(ComponentRemoveCommand.type, ComponentRemoveCommand); +commandsByType.set(EntityCreateCommand.type, EntityCreateCommand); +commandsByType.set(EntityRemoveCommand.type, EntityRemoveCommand); diff --git a/src/editor/lib/entity.js b/src/editor/lib/entity.js index bfa0b325a..3fc818b12 100644 --- a/src/editor/lib/entity.js +++ b/src/editor/lib/entity.js @@ -1,7 +1,6 @@ /* eslint-disable react/no-danger */ import { nanoid } from 'nanoid'; import Events from './Events'; -import { EntityUpdateCommand, EntityRemoveCommand } from './commands'; import { equal } from './utils'; /** @@ -19,14 +18,12 @@ export function updateEntity(entity, propertyName, value) { splitName = propertyName.split('.'); } - AFRAME.INSPECTOR.execute( - new EntityUpdateCommand(AFRAME.INSPECTOR, { - entity: entity, - component: splitName ? splitName[0] : propertyName, - property: splitName ? splitName[1] : '', - value: value - }) - ); + AFRAME.INSPECTOR.execute('entityupdate', { + entity: entity, + component: splitName ? splitName[0] : propertyName, + property: splitName ? splitName[1] : '', + value: value + }); } /** @@ -45,8 +42,7 @@ export function removeEntity(entity, force) { '`?' ) ) { - const command = new EntityRemoveCommand(AFRAME.INSPECTOR, entity); - AFRAME.INSPECTOR.execute(command); + AFRAME.INSPECTOR.execute('entityremove', entity); } } } diff --git a/src/editor/lib/viewport.js b/src/editor/lib/viewport.js index a4be475cb..2fccdcf8a 100644 --- a/src/editor/lib/viewport.js +++ b/src/editor/lib/viewport.js @@ -5,7 +5,6 @@ import EditorControls from './EditorControls.js'; import { initRaycaster } from './raycaster'; import Events from './Events'; import { sendMetric } from '../services/ga.js'; -import { EntityUpdateCommand } from './commands/EntityUpdateCommand.js'; // variables used by OrientedBoxHelper const auxEuler = new THREE.Euler(); @@ -178,14 +177,12 @@ export function Viewport(inspector) { value = `${object.scale.x} ${object.scale.y} ${object.scale.z}`; } - inspector.execute( - new EntityUpdateCommand(inspector, { - component: component, - entity: transformControls.object.el, - property: '', - value: value - }) - ); + inspector.execute('entityupdate', { + component: component, + entity: transformControls.object.el, + property: '', + value: value + }); }); transformControls.addEventListener('mouseDown', () => { From 24f24831963a5b737d6db6b0e2682f3fc7430adc Mon Sep 17 00:00:00 2001 From: Vincent Fretin Date: Tue, 27 Aug 2024 17:25:17 +0200 Subject: [PATCH 18/49] return in execute so we get the created entity for entitycreate command --- src/editor/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/editor/index.js b/src/editor/index.js index b339c1a11..ff3b3dcf9 100644 --- a/src/editor/index.js +++ b/src/editor/index.js @@ -236,7 +236,7 @@ Inspector.prototype = { console.error(`Command ${cmdName} not found`); return; } - this.history.execute(new Cmd(this, payload), optionalName); + return this.history.execute(new Cmd(this, payload), optionalName); }, undo: function () { From 4927b8b76ff255a530f9fe7a7da49ba3a4ab8c8e Mon Sep 17 00:00:00 2001 From: Vincent Fretin Date: Tue, 27 Aug 2024 17:59:02 +0200 Subject: [PATCH 19/49] recreate createEntity and use it in the entitycreate command --- .../lib/commands/EntityCreateCommand.js | 52 ++------------- .../lib/commands/EntityRemoveCommand.js | 2 +- src/editor/lib/entity.js | 63 ++++++++++++++++++- 3 files changed, 67 insertions(+), 50 deletions(-) diff --git a/src/editor/lib/commands/EntityCreateCommand.js b/src/editor/lib/commands/EntityCreateCommand.js index 6d6dde4a4..32f4b9528 100644 --- a/src/editor/lib/commands/EntityCreateCommand.js +++ b/src/editor/lib/commands/EntityCreateCommand.js @@ -1,8 +1,6 @@ import Events from '../Events'; import { Command } from '../command.js'; -import { createUniqueId } from '../entity.js'; - -const NOT_COMPONENTS = ['id', 'class', 'mixin']; +import { createEntity } from '../entity.js'; /** * Helper function to add a new entity with a list of components @@ -22,51 +20,13 @@ export class EntityCreateCommand extends Command { execute() { const definition = this.definition; - this.entity = document.createElement(definition.element || 'a-entity'); - const entity = this.entity; - - // Set id - if (definition.id) { - entity.id = definition.id; - } else { - this.entity.id = createUniqueId(); - } - - // Set class, mixin - for (const attribute of NOT_COMPONENTS) { - if (attribute !== 'id' && definition[attribute]) { - entity.setAttribute(attribute, definition[attribute]); - } - } - - // Set data attributes - for (const key in definition) { - if (key.startsWith('data-')) { - entity.setAttribute(key, definition[key]); - } - } - - // Set components - for (const componentName in definition.components) { - const componentValue = definition.components[componentName]; - entity.setAttribute(componentName, componentValue); - } - - // Emit event after entity is loaded - this.entity.addEventListener( - 'loaded', - () => { - this.editor.selectEntity(this.entity); - Events.emit('entitycreated', this.entity); - }, - { once: true } - ); - - // Add to parentEl if defined of fallback to scene container + const callback = (entity) => { + this.editor.selectEntity(entity); + }; const parentEl = this.definition.parentEl ?? document.querySelector('#street-container'); - parentEl.appendChild(this.entity); - return entity; + this.entity = createEntity(definition, callback, parentEl); + return this.entity; } undo() { diff --git a/src/editor/lib/commands/EntityRemoveCommand.js b/src/editor/lib/commands/EntityRemoveCommand.js index 47baa6e27..e5cf9c864 100644 --- a/src/editor/lib/commands/EntityRemoveCommand.js +++ b/src/editor/lib/commands/EntityRemoveCommand.js @@ -43,8 +43,8 @@ export class EntityRemoveCommand extends Command { this.entity.addEventListener( 'loaded', () => { - this.editor.selectEntity(this.entity); Events.emit('entitycreated', this.entity); + this.editor.selectEntity(this.entity); }, { once: true } ); diff --git a/src/editor/lib/entity.js b/src/editor/lib/entity.js index 3fc818b12..2802289d8 100644 --- a/src/editor/lib/entity.js +++ b/src/editor/lib/entity.js @@ -469,9 +469,9 @@ function getUniqueId(baseId) { } /** - * Create a unique id that can be used on a DOM element. - * @return {string} Valid Id - */ + * Create a unique id that can be used on a DOM element. + * @return {string} Valid Id + */ export function createUniqueId() { let id = nanoid(); do { @@ -570,3 +570,60 @@ export function printEntity(entity, onDoubleClick) { ); } + +const NOT_COMPONENTS = ['id', 'class', 'mixin']; + +/** + * Helper function to add a new entity with a list of components + * @param {object} definition Entity definition to add, only components is required: + * {element: 'a-entity', id: "hbiuSdYL2", class: "box", components: {geometry: 'primitive:box'}} + * @param {function} cb Callback to call when the entity is created + * @param {Element} parentEl Element to append the entity to + * @return {Element} Entity created + */ +export function createEntity(definition, cb, parentEl = undefined) { + const entity = document.createElement(definition.element || 'a-entity'); + if (definition.id) { + entity.id = definition.id; + } else { + entity.id = createUniqueId(); + } + + // Set class, mixin + for (const attribute of NOT_COMPONENTS) { + if (attribute !== 'id' && definition[attribute]) { + entity.setAttribute(attribute, definition[attribute]); + } + } + + // Set data attributes + for (const key in definition) { + if (key.startsWith('data-')) { + entity.setAttribute(key, definition[key]); + } + } + + // Set components + for (const componentName in definition.components) { + const componentValue = definition.components[componentName]; + entity.setAttribute(componentName, componentValue); + } + + // Ensure the components are loaded before update the UI + entity.addEventListener( + 'loaded', + () => { + Events.emit('entitycreated', entity); + cb(entity); + }, + { once: true } + ); + + if (parentEl) { + parentEl.appendChild(entity); + } else { + document.getElementById('street-container').appendChild(entity); + } + + return entity; +} From 46724a09cebd9f9c0586e77f26c865e016955b63 Mon Sep 17 00:00:00 2001 From: Vincent Fretin Date: Tue, 27 Aug 2024 18:16:42 +0200 Subject: [PATCH 20/49] simplify updateEntity function used in PropertyRow component and move updateEntity from entityupdate command to lib/entity.js --- .../components/components/PropertyRow.js | 14 ++++---- .../lib/commands/EntityUpdateCommand.js | 22 +------------ src/editor/lib/entity.js | 33 +++++++++++-------- 3 files changed, 26 insertions(+), 43 deletions(-) diff --git a/src/editor/components/components/PropertyRow.js b/src/editor/components/components/PropertyRow.js index bc8e1da6b..493a7a570 100644 --- a/src/editor/components/components/PropertyRow.js +++ b/src/editor/components/components/PropertyRow.js @@ -12,7 +12,6 @@ import TextureWidget from '../widgets/TextureWidget'; import Vec4Widget from '../widgets/Vec4Widget'; import Vec3Widget from '../widgets/Vec3Widget'; import Vec2Widget from '../widgets/Vec2Widget'; -import { updateEntity } from '../../lib/entity'; export default class PropertyRow extends React.Component { static propTypes = { @@ -56,14 +55,13 @@ export default class PropertyRow extends React.Component { entity: props.entity, isSingle: props.isSingle, name: props.name, - // Wrap updateEntity for tracking. onChange: function (name, value) { - var propertyName = props.componentname; - if (!props.isSingle) { - propertyName += '.' + props.name; - } - - updateEntity.apply(this, [props.entity, propertyName, value]); + AFRAME.INSPECTOR.execute('entityupdate', { + entity: props.entity, + component: props.componentname, + property: !props.isSingle ? props.name : '', + value: value + }); }, value: value }; diff --git a/src/editor/lib/commands/EntityUpdateCommand.js b/src/editor/lib/commands/EntityUpdateCommand.js index 2afffe9c1..e1df24f1d 100644 --- a/src/editor/lib/commands/EntityUpdateCommand.js +++ b/src/editor/lib/commands/EntityUpdateCommand.js @@ -1,26 +1,6 @@ import Events from '../Events'; import { Command } from '../command.js'; -import { createUniqueId } from '../entity.js'; - -function updateEntity(entity, component, property, value) { - if (property) { - if (value === null || value === undefined) { - // Remove property. - entity.removeAttribute(component, property); - } else { - // Set property. - entity.setAttribute(component, property, value); - } - } else { - if (value === null || value === undefined) { - // Remove component. - entity.removeAttribute(component); - } else { - // Set component. - entity.setAttribute(component, value); - } - } -} +import { createUniqueId, updateEntity } from '../entity.js'; /** * @param editor Editor diff --git a/src/editor/lib/entity.js b/src/editor/lib/entity.js index 2802289d8..471bd8a8b 100644 --- a/src/editor/lib/entity.js +++ b/src/editor/lib/entity.js @@ -7,23 +7,28 @@ import { equal } from './utils'; * Update a component. * * @param {Element} entity - Entity to modify. - * @param {string} propertyName - component or component.property + * @param {string} component - component name + * @param {string} property - property name, use empty string if component is not a multi-property * @param {string|number} value - New value. */ -export function updateEntity(entity, propertyName, value) { - var splitName; - - if (propertyName.indexOf('.') !== -1) { - // Multi-prop - splitName = propertyName.split('.'); +export function updateEntity(entity, component, property, value) { + if (property) { + if (value === null || value === undefined) { + // Remove property. + entity.removeAttribute(component, property); + } else { + // Set property. + entity.setAttribute(component, property, value); + } + } else { + if (value === null || value === undefined) { + // Remove component. + entity.removeAttribute(component); + } else { + // Set component. + entity.setAttribute(component, value); + } } - - AFRAME.INSPECTOR.execute('entityupdate', { - entity: entity, - component: splitName ? splitName[0] : propertyName, - property: splitName ? splitName[1] : '', - value: value - }); } /** From 4172a79111162ec3476e99d6e5bdc6e28c013396 Mon Sep 17 00:00:00 2001 From: Vincent Fretin Date: Tue, 27 Aug 2024 19:02:56 +0200 Subject: [PATCH 21/49] move emit entityupdate in updateEntity --- src/editor/lib/commands/EntityUpdateCommand.js | 13 ------------- src/editor/lib/entity.js | 2 ++ 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/src/editor/lib/commands/EntityUpdateCommand.js b/src/editor/lib/commands/EntityUpdateCommand.js index e1df24f1d..722fe7e03 100644 --- a/src/editor/lib/commands/EntityUpdateCommand.js +++ b/src/editor/lib/commands/EntityUpdateCommand.js @@ -1,4 +1,3 @@ -import Events from '../Events'; import { Command } from '../command.js'; import { createUniqueId, updateEntity } from '../entity.js'; @@ -71,12 +70,6 @@ export class EntityUpdateCommand extends Command { ); } updateEntity(this.entity, this.component, this.property, this.newValue); - Events.emit('entityupdate', { - entity: this.entity, - component: this.component, - property: this.property, - value: this.newValue - }); } undo() { @@ -93,12 +86,6 @@ export class EntityUpdateCommand extends Command { this.editor.selectEntity(this.entity); } updateEntity(this.entity, this.component, this.property, this.oldValue); - Events.emit('entityupdate', { - entity: this.entity, - component: this.component, - property: this.property, - value: this.oldValue - }); } update(command) { diff --git a/src/editor/lib/entity.js b/src/editor/lib/entity.js index 471bd8a8b..acf730c56 100644 --- a/src/editor/lib/entity.js +++ b/src/editor/lib/entity.js @@ -29,6 +29,8 @@ export function updateEntity(entity, component, property, value) { entity.setAttribute(component, value); } } + + Events.emit('entityupdate', { entity, component, property, value }); } /** From e3bb79e64836c2756aaa211fcb39b06da1dbacac Mon Sep 17 00:00:00 2001 From: Vincent Fretin Date: Tue, 27 Aug 2024 19:49:03 +0200 Subject: [PATCH 22/49] Remove redundant removeObject call, this is already called on child-detached --- src/editor/lib/commands/EntityCreateCommand.js | 1 - src/editor/lib/commands/EntityRemoveCommand.js | 1 - 2 files changed, 2 deletions(-) diff --git a/src/editor/lib/commands/EntityCreateCommand.js b/src/editor/lib/commands/EntityCreateCommand.js index 32f4b9528..ccdc8379c 100644 --- a/src/editor/lib/commands/EntityCreateCommand.js +++ b/src/editor/lib/commands/EntityCreateCommand.js @@ -32,7 +32,6 @@ export class EntityCreateCommand extends Command { undo() { if (this.entity) { this.editor.selectEntity(null); - this.editor.removeObject(this.entity.object3D); this.entity.parentNode.removeChild(this.entity); Events.emit('entityremoved', this.entity); this.entity = null; diff --git a/src/editor/lib/commands/EntityRemoveCommand.js b/src/editor/lib/commands/EntityRemoveCommand.js index e5cf9c864..62b02dac0 100644 --- a/src/editor/lib/commands/EntityRemoveCommand.js +++ b/src/editor/lib/commands/EntityRemoveCommand.js @@ -24,7 +24,6 @@ export class EntityRemoveCommand extends Command { const clone = prepareForSerialization(this.entity); // Remove entity - this.editor.removeObject(this.entity.object3D); this.entity.parentNode.removeChild(this.entity); Events.emit('entityremoved', this.entity); From ce177fe1dedc16bf069e615b2991fa573cdfdee6 Mon Sep 17 00:00:00 2001 From: Vincent Fretin Date: Tue, 27 Aug 2024 20:10:55 +0200 Subject: [PATCH 23/49] Remove unneeded iife in addHelper --- src/editor/index.js | 56 ++++++++++++++++++++++----------------------- 1 file changed, 27 insertions(+), 29 deletions(-) diff --git a/src/editor/index.js b/src/editor/index.js index ff3b3dcf9..f0eae145f 100644 --- a/src/editor/index.js +++ b/src/editor/index.js @@ -107,36 +107,34 @@ Inspector.prototype = { Events.emit('objectremove', object); }, - addHelper: (function () { - return 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; - } + 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(); - } - }; - })(), + 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) => { From 6e654f946565c5dde64d44e3e6f6cc45dca79fdf Mon Sep 17 00:00:00 2001 From: Vincent Fretin Date: Tue, 27 Aug 2024 20:11:50 +0200 Subject: [PATCH 24/49] Use const to be consistent in this file --- src/editor/index.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/editor/index.js b/src/editor/index.js index f0eae145f..cea086c07 100644 --- a/src/editor/index.js +++ b/src/editor/index.js @@ -161,7 +161,7 @@ Inspector.prototype = { } // Update helper visibilities. - for (let id in this.helpers) { + for (const id in this.helpers) { this.helpers[id].visible = false; } @@ -181,7 +181,7 @@ Inspector.prototype = { initEvents: function () { window.addEventListener('keydown', (evt) => { // Alt + Ctrl + i: Shorcut to toggle the inspector - var shortcutPressed = + const shortcutPressed = evt.keyCode === 73 && ((evt.ctrlKey && evt.altKey) || evt.getModifierState('AltGraph')); if (shortcutPressed) { @@ -223,7 +223,7 @@ Inspector.prototype = { }); document.addEventListener('child-detached', (event) => { - var entity = event.detail.el; + const entity = event.detail.el; AFRAME.INSPECTOR.removeObject(entity.object3D); }); }, From 5e35c74e2d57c392032d421efc5c7099b863db1f Mon Sep 17 00:00:00 2001 From: Vincent Fretin Date: Wed, 28 Aug 2024 10:37:01 +0200 Subject: [PATCH 25/49] use geoLayer variable instead of getting again the entity --- .../components/AddLayerPanel/createLayerFunctions.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/editor/components/components/AddLayerPanel/createLayerFunctions.js b/src/editor/components/components/AddLayerPanel/createLayerFunctions.js index cced04707..4f12d288d 100644 --- a/src/editor/components/components/AddLayerPanel/createLayerFunctions.js +++ b/src/editor/components/components/AddLayerPanel/createLayerFunctions.js @@ -30,9 +30,7 @@ export function createMapbox() { const geoLayer = document.getElementById('reference-layers'); let latitude = 0; let longitude = 0; - const streetGeo = document - .getElementById('reference-layers') - ?.getAttribute('street-geo'); + const streetGeo = geoLayer?.getAttribute('street-geo'); if (streetGeo && streetGeo['latitude'] && streetGeo['longitude']) { latitude = roundCoord(parseFloat(streetGeo['latitude'])); From 147814253510a28028952a295a2454f25d785fc9 Mon Sep 17 00:00:00 2001 From: Vincent Fretin Date: Wed, 28 Aug 2024 10:38:13 +0200 Subject: [PATCH 26/49] use entityupdate or componentadd command when updating geo coordinates --- .../modals/GeoModal/GeoModal.component.jsx | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/src/editor/components/modals/GeoModal/GeoModal.component.jsx b/src/editor/components/modals/GeoModal/GeoModal.component.jsx index d77b229f9..fe1eeb737 100644 --- a/src/editor/components/modals/GeoModal/GeoModal.component.jsx +++ b/src/editor/components/modals/GeoModal/GeoModal.component.jsx @@ -136,10 +136,28 @@ const GeoModal = ({ isOpen, onClose }) => { console.log(`elevation: ${data.ellipsoidalHeight}`); const geoLayer = document.getElementById('reference-layers'); - geoLayer.setAttribute( - 'street-geo', - `latitude: ${latitude}; longitude: ${longitude}; ellipsoidalHeight: ${data.ellipsoidalHeight}; orthometricHeight: ${data.orthometricHeight}; geoidHeight: ${data.geoidHeight}` - ); + const value = { + latitude: latitude, + longitude: longitude, + ellipsoidalHeight: data.ellipsoidalHeight, + orthometricHeight: data.orthometricHeight, + geoidHeight: data.geoidHeight + }; + + if (geoLayer.hasAttribute('street-geo')) { + AFRAME.INSPECTOR.execute('entityupdate', { + entity: geoLayer, + component: 'street-geo', + property: '', + value + }); + } else { + AFRAME.INSPECTOR.execute('componentadd', { + entity: geoLayer, + component: 'street-geo', + value + }); + } } setIsWorking(false); From 0ca98b3c2e1890e77038e3bba48cb4f0587b9e27 Mon Sep 17 00:00:00 2001 From: Vincent Fretin Date: Wed, 28 Aug 2024 10:56:51 +0200 Subject: [PATCH 27/49] fix entityupdate on multi-prop component with property '' and value as object --- .../AddLayerPanel/createLayerFunctions.js | 2 -- .../modals/GeoModal/GeoModal.component.jsx | 1 - src/editor/lib/commands/EntityUpdateCommand.js | 15 ++++++++++----- src/editor/lib/viewport.js | 1 - 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/editor/components/components/AddLayerPanel/createLayerFunctions.js b/src/editor/components/components/AddLayerPanel/createLayerFunctions.js index 4f12d288d..770085805 100644 --- a/src/editor/components/components/AddLayerPanel/createLayerFunctions.js +++ b/src/editor/components/components/AddLayerPanel/createLayerFunctions.js @@ -41,7 +41,6 @@ export function createMapbox() { AFRAME.INSPECTOR.execute('entityupdate', { entity: geoLayer, component: 'street-geo', - property: '', value: { latitude: latitude, longitude: longitude, @@ -145,7 +144,6 @@ export function create3DTiles() { AFRAME.INSPECTOR.execute('entityupdate', { entity: geoLayer, component: 'street-geo', - property: '', value: { latitude: latitude, longitude: longitude, diff --git a/src/editor/components/modals/GeoModal/GeoModal.component.jsx b/src/editor/components/modals/GeoModal/GeoModal.component.jsx index fe1eeb737..fc07a53f7 100644 --- a/src/editor/components/modals/GeoModal/GeoModal.component.jsx +++ b/src/editor/components/modals/GeoModal/GeoModal.component.jsx @@ -148,7 +148,6 @@ const GeoModal = ({ isOpen, onClose }) => { AFRAME.INSPECTOR.execute('entityupdate', { entity: geoLayer, component: 'street-geo', - property: '', value }); } else { diff --git a/src/editor/lib/commands/EntityUpdateCommand.js b/src/editor/lib/commands/EntityUpdateCommand.js index 722fe7e03..954ebf1c4 100644 --- a/src/editor/lib/commands/EntityUpdateCommand.js +++ b/src/editor/lib/commands/EntityUpdateCommand.js @@ -19,7 +19,7 @@ export class EntityUpdateCommand extends Command { this.entity.id = createUniqueId(); } this.component = payload.component; - this.property = payload.property; + this.property = payload.property ?? ''; const component = this.entity.components[payload.component] ?? @@ -48,10 +48,15 @@ export class EntityUpdateCommand extends Command { console.log(this.component, this.oldValue, this.newValue); } } else { - this.newValue = component.schema.stringify(payload.value); - this.oldValue = component.schema.stringify( - payload.entity.getAttribute(payload.component) - ); + // If component.schema.stringify is undefined then it's multi-property component and value is an object. + this.newValue = component.schema.stringify + ? component.schema.stringify(payload.value) + : payload.value; + this.oldValue = component.schema.stringify + ? component.schema.stringify( + payload.entity.getAttribute(payload.component) + ) + : payload.entity.getAttribute(payload.component); if (this.editor.debugUndoRedo) { console.log(this.component, this.oldValue, this.newValue); } diff --git a/src/editor/lib/viewport.js b/src/editor/lib/viewport.js index 2fccdcf8a..b8a05101e 100644 --- a/src/editor/lib/viewport.js +++ b/src/editor/lib/viewport.js @@ -180,7 +180,6 @@ export function Viewport(inspector) { inspector.execute('entityupdate', { component: component, entity: transformControls.object.el, - property: '', value: value }); }); From 8f41a6f36886d66cd9a6f9f0cfb83ddec0741634 Mon Sep 17 00:00:00 2001 From: Vincent Fretin Date: Wed, 28 Aug 2024 11:08:53 +0200 Subject: [PATCH 28/49] revert back to this.type instead of static type --- src/editor/lib/commands/ComponentAddCommand.js | 2 +- src/editor/lib/commands/ComponentRemoveCommand.js | 2 +- src/editor/lib/commands/EntityCreateCommand.js | 2 +- src/editor/lib/commands/EntityRemoveCommand.js | 2 +- src/editor/lib/commands/EntityUpdateCommand.js | 2 +- src/editor/lib/commands/index.js | 10 +++++----- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/editor/lib/commands/ComponentAddCommand.js b/src/editor/lib/commands/ComponentAddCommand.js index 1c851b6f6..1803164e5 100644 --- a/src/editor/lib/commands/ComponentAddCommand.js +++ b/src/editor/lib/commands/ComponentAddCommand.js @@ -3,10 +3,10 @@ import { Command } from '../command.js'; import { createUniqueId } from '../entity.js'; export class ComponentAddCommand extends Command { - static type = 'componentadd'; constructor(editor, payload) { super(editor); + this.type = 'componentadd'; this.name = 'Add Component'; this.updatable = false; diff --git a/src/editor/lib/commands/ComponentRemoveCommand.js b/src/editor/lib/commands/ComponentRemoveCommand.js index 1d8f2c441..cc5fd4570 100644 --- a/src/editor/lib/commands/ComponentRemoveCommand.js +++ b/src/editor/lib/commands/ComponentRemoveCommand.js @@ -3,10 +3,10 @@ import { Command } from '../command.js'; import { createUniqueId } from '../entity.js'; export class ComponentRemoveCommand extends Command { - static type = 'componentremove'; constructor(editor, payload) { super(editor); + this.type = 'componentremove'; this.name = 'Remove Component'; this.updatable = false; diff --git a/src/editor/lib/commands/EntityCreateCommand.js b/src/editor/lib/commands/EntityCreateCommand.js index ccdc8379c..35ea63658 100644 --- a/src/editor/lib/commands/EntityCreateCommand.js +++ b/src/editor/lib/commands/EntityCreateCommand.js @@ -9,10 +9,10 @@ import { createEntity } from '../entity.js'; * @return {Element} Entity created */ export class EntityCreateCommand extends Command { - static type = 'entitycreate'; constructor(editor, definition) { super(editor); + this.type = 'entitycreate'; this.name = 'Create Entity'; this.definition = definition; this.entity = null; diff --git a/src/editor/lib/commands/EntityRemoveCommand.js b/src/editor/lib/commands/EntityRemoveCommand.js index 62b02dac0..8abbeadc5 100644 --- a/src/editor/lib/commands/EntityRemoveCommand.js +++ b/src/editor/lib/commands/EntityRemoveCommand.js @@ -3,10 +3,10 @@ import { Command } from '../command.js'; import { findClosestEntity, prepareForSerialization } from '../entity.js'; export class EntityRemoveCommand extends Command { - static type = 'entityremove'; constructor(editor, entity) { super(editor); + this.type = 'entityremove'; this.name = 'Remove Entity'; this.updatable = false; diff --git a/src/editor/lib/commands/EntityUpdateCommand.js b/src/editor/lib/commands/EntityUpdateCommand.js index 954ebf1c4..c49a9d7c7 100644 --- a/src/editor/lib/commands/EntityUpdateCommand.js +++ b/src/editor/lib/commands/EntityUpdateCommand.js @@ -7,10 +7,10 @@ import { createUniqueId, updateEntity } from '../entity.js'; * @constructor */ export class EntityUpdateCommand extends Command { - static type = 'entityupdate'; constructor(editor, payload) { super(editor); + this.type = 'entityupdate'; this.name = 'Update Entity'; this.updatable = true; diff --git a/src/editor/lib/commands/index.js b/src/editor/lib/commands/index.js index 492bd58a0..3c04a2266 100644 --- a/src/editor/lib/commands/index.js +++ b/src/editor/lib/commands/index.js @@ -5,8 +5,8 @@ import { EntityCreateCommand } from './EntityCreateCommand.js'; import { EntityRemoveCommand } from './EntityRemoveCommand.js'; export const commandsByType = new Map(); -commandsByType.set(EntityUpdateCommand.type, EntityUpdateCommand); -commandsByType.set(ComponentAddCommand.type, ComponentAddCommand); -commandsByType.set(ComponentRemoveCommand.type, ComponentRemoveCommand); -commandsByType.set(EntityCreateCommand.type, EntityCreateCommand); -commandsByType.set(EntityRemoveCommand.type, EntityRemoveCommand); +commandsByType.set('entityupdate', EntityUpdateCommand); +commandsByType.set('componentadd', ComponentAddCommand); +commandsByType.set('componentremove', ComponentRemoveCommand); +commandsByType.set('entitycreate', EntityCreateCommand); +commandsByType.set('entityremove', EntityRemoveCommand); From e4c610b3fdc8709ac219ad093363e427b8e05c54 Mon Sep 17 00:00:00 2001 From: Vincent Fretin Date: Wed, 28 Aug 2024 11:32:53 +0200 Subject: [PATCH 29/49] always get back the entity from id, with undo remove the entity was the old one --- .../lib/commands/ComponentAddCommand.js | 38 +++++++------- .../lib/commands/ComponentRemoveCommand.js | 40 ++++++++------- .../lib/commands/EntityCreateCommand.js | 15 +++--- .../lib/commands/EntityUpdateCommand.js | 51 +++++++++---------- src/editor/lib/history.js | 2 +- 5 files changed, 75 insertions(+), 71 deletions(-) diff --git a/src/editor/lib/commands/ComponentAddCommand.js b/src/editor/lib/commands/ComponentAddCommand.js index 1803164e5..11528b54b 100644 --- a/src/editor/lib/commands/ComponentAddCommand.js +++ b/src/editor/lib/commands/ComponentAddCommand.js @@ -10,33 +10,35 @@ export class ComponentAddCommand extends Command { this.name = 'Add Component'; this.updatable = false; - this.entity = payload.entity; - if (!this.entity.id) { - this.entity.id = createUniqueId(); + const entity = payload.entity; + if (!entity.id) { + entity.id = createUniqueId(); } + this.entityId = entity.id; this.component = payload.component; this.value = payload.value; } execute() { - this.entity.setAttribute(this.component, this.value); - Events.emit('componentadd', { - entity: this.entity, - component: this.component, - value: this.value - }); + const entity = document.getElementById(this.entityId); + if (entity) { + entity.setAttribute(this.component, this.value); + Events.emit('componentadd', { + entity: entity, + component: this.component, + value: this.value + }); + } } undo() { - // Get again the entity from id, the entity may have been recreated if it was removed then undone. - const entity = document.getElementById(this.entity.id); - if (this.entity !== entity) { - this.entity = entity; + const entity = document.getElementById(this.entityId); + if (entity) { + entity.removeAttribute(this.component); + Events.emit('componentremove', { + entity, + component: this.component + }); } - this.entity.removeAttribute(this.component); - Events.emit('componentremove', { - entity: this.entity, - component: this.component - }); } } diff --git a/src/editor/lib/commands/ComponentRemoveCommand.js b/src/editor/lib/commands/ComponentRemoveCommand.js index cc5fd4570..c4e7c9315 100644 --- a/src/editor/lib/commands/ComponentRemoveCommand.js +++ b/src/editor/lib/commands/ComponentRemoveCommand.js @@ -10,33 +10,35 @@ export class ComponentRemoveCommand extends Command { this.name = 'Remove Component'; this.updatable = false; - this.entity = payload.entity; - if (!this.entity.id) { - this.entity.id = createUniqueId(); + const entity = payload.entity; + if (!entity.id) { + entity.id = createUniqueId(); } + this.entityId = entity.id; this.component = payload.component; - this.value = this.entity.getAttribute(this.component); + this.value = entity.getAttribute(this.component); } execute() { - this.entity.removeAttribute(this.component); - Events.emit('componentremove', { - entity: this.entity, - component: this.component - }); + const entity = document.getElementById(this.entityId); + if (entity) { + entity.removeAttribute(this.component); + Events.emit('componentremove', { + entity, + component: this.component + }); + } } undo() { - // Get again the entity from id, the entity may have been recreated if it was removed then undone. - const entity = document.getElementById(this.entity.id); - if (this.entity !== entity) { - this.entity = entity; + const entity = document.getElementById(this.entityId); + if (entity) { + entity.setAttribute(this.component, this.value); + Events.emit('componentadd', { + entity, + component: this.component, + value: this.value + }); } - this.entity.setAttribute(this.component, this.value); - Events.emit('componentadd', { - entity: this.entity, - component: this.component, - value: this.value - }); } } diff --git a/src/editor/lib/commands/EntityCreateCommand.js b/src/editor/lib/commands/EntityCreateCommand.js index 35ea63658..9ae7ab093 100644 --- a/src/editor/lib/commands/EntityCreateCommand.js +++ b/src/editor/lib/commands/EntityCreateCommand.js @@ -15,7 +15,7 @@ export class EntityCreateCommand extends Command { this.type = 'entitycreate'; this.name = 'Create Entity'; this.definition = definition; - this.entity = null; + this.entityId = null; } execute() { @@ -25,16 +25,17 @@ export class EntityCreateCommand extends Command { }; const parentEl = this.definition.parentEl ?? document.querySelector('#street-container'); - this.entity = createEntity(definition, callback, parentEl); - return this.entity; + const entity = createEntity(definition, callback, parentEl); + this.entityId = entity.id; + return entity; } undo() { - if (this.entity) { + const entity = document.getElementById(this.entityId); + if (entity) { this.editor.selectEntity(null); - this.entity.parentNode.removeChild(this.entity); - Events.emit('entityremoved', this.entity); - this.entity = null; + entity.parentNode.removeChild(entity); + Events.emit('entityremoved', entity); } } } diff --git a/src/editor/lib/commands/EntityUpdateCommand.js b/src/editor/lib/commands/EntityUpdateCommand.js index c49a9d7c7..90f9be68c 100644 --- a/src/editor/lib/commands/EntityUpdateCommand.js +++ b/src/editor/lib/commands/EntityUpdateCommand.js @@ -14,17 +14,18 @@ export class EntityUpdateCommand extends Command { this.name = 'Update Entity'; this.updatable = true; - this.entity = payload.entity; - if (!this.entity.id) { - this.entity.id = createUniqueId(); + const entity = payload.entity; + if (!entity.id) { + entity.id = createUniqueId(); } + this.entityId = entity.id; this.component = payload.component; this.property = payload.property ?? ''; const component = - this.entity.components[payload.component] ?? + entity.components[payload.component] ?? AFRAME.components[payload.component]; - // First try to get `this.entity.components[payload.component]` to have the dynamic schema, and fallback to `AFRAME.components[payload.component]` if not found. + // First try to get `entity.components[payload.component]` to have the dynamic schema, and fallback to `AFRAME.components[payload.component]` if not found. // This is to properly stringify some properties that uses for example vec2 or vec3 on material component. // This is important to fallback to `AFRAME.components[payload.component]` for primitive components position rotation and scale // that may not have been created initially on the entity. @@ -65,32 +66,30 @@ export class EntityUpdateCommand extends Command { } execute() { - if (this.editor.debugUndoRedo) { - console.log( - 'execute', - this.entity, - this.component, - this.property, - this.newValue - ); + const entity = document.getElementById(this.entityId); + if (entity) { + if (this.editor.debugUndoRedo) { + console.log( + 'execute', + entity, + this.component, + this.property, + this.newValue + ); + } + updateEntity(entity, this.component, this.property, this.newValue); } - updateEntity(this.entity, this.component, this.property, this.newValue); } undo() { - // Get again the entity from id, the entity may have been recreated if it was removed then undone. - const entity = document.getElementById(this.entity.id); - if (this.entity !== entity) { - this.entity = entity; - } - if ( - this.editor.selectedEntity && - this.editor.selectedEntity !== this.entity - ) { - // If the selected entity is not the entity we are undoing, select the entity. - this.editor.selectEntity(this.entity); + const entity = document.getElementById(this.entityId); + if (entity) { + if (this.editor.selectedEntity && this.editor.selectedEntity !== entity) { + // If the selected entity is not the entity we are undoing, select the entity. + this.editor.selectEntity(entity); + } + updateEntity(entity, this.component, this.property, this.oldValue); } - updateEntity(this.entity, this.component, this.property, this.oldValue); } update(command) { diff --git a/src/editor/lib/history.js b/src/editor/lib/history.js index 2fe679557..6b40f2857 100644 --- a/src/editor/lib/history.js +++ b/src/editor/lib/history.js @@ -23,7 +23,7 @@ export class History { lastCmd && lastCmd.updatable && cmd.updatable && - lastCmd.entity === cmd.entity && + lastCmd.entityId === cmd.entityId && lastCmd.type === cmd.type && lastCmd.component === cmd.component && lastCmd.property === cmd.property; From 303463da0d7b00824fca4cc131640722a3e1fc46 Mon Sep 17 00:00:00 2001 From: Vincent Fretin Date: Wed, 28 Aug 2024 11:59:13 +0200 Subject: [PATCH 30/49] remove non working line --- src/json-utils_1.1.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/json-utils_1.1.js b/src/json-utils_1.1.js index 3fe4011a4..90c7b240d 100644 --- a/src/json-utils_1.1.js +++ b/src/json-utils_1.1.js @@ -443,9 +443,6 @@ function createEntityFromObj(entityData, parentEl) { if (entityData.mixin) { entity.setAttribute('mixin', entityData.mixin); } - // Ensure the components are loaded before update the UI - - entity.emit('entitycreated', {}, false); }); if (entityData.children) { From 69904017a9557f80e0881f8bb641fd655c50d28e Mon Sep 17 00:00:00 2001 From: Vincent Fretin Date: Wed, 28 Aug 2024 13:26:55 +0200 Subject: [PATCH 31/49] show default google 3d tiles in scene graph after setting location (fix #770) --- src/components/street-geo.js | 35 +++++++++++++++++++++++++++++++---- src/editor/index.js | 4 +++- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/src/components/street-geo.js b/src/components/street-geo.js index ee181d254..026b23698 100644 --- a/src/components/street-geo.js +++ b/src/components/street-geo.js @@ -65,9 +65,15 @@ AFRAME.registerComponent('street-geo', { } else if (this[mapType] && data.maps !== mapType) { // remove element from DOM and from this object this.el.removeChild(this[mapType]); + if (AFRAME.INSPECTOR?.opened) { + AFRAME.INSPECTOR.emit('entityremoved', this[mapType]); + } this[mapType] = null; if (mapType === 'osm3d') { this.el.removeChild(this['osm3dBuilding']); + if (AFRAME.INSPECTOR?.opened) { + AFRAME.INSPECTOR.emit('entityremoved', this['osm3dBuilding']); + } } } } @@ -97,6 +103,15 @@ AFRAME.registerComponent('street-geo', { }); mapbox2dElement.classList.add('autocreated'); mapbox2dElement.setAttribute('data-ignore-raycaster', ''); + if (AFRAME.INSPECTOR?.opened) { + mapbox2dElement.addEventListener( + 'loaded', + () => { + AFRAME.INSPECTOR.emit('entitycreated', mapbox2dElement); + }, + { once: true } + ); + } el.appendChild(mapbox2dElement); this['mapbox2d'] = mapbox2dElement; document.getElementById('map-copyright').textContent = 'MapBox'; @@ -129,11 +144,12 @@ AFRAME.registerComponent('street-geo', { }); google3dElement.classList.add('autocreated'); - if (AFRAME.INSPECTOR && AFRAME.INSPECTOR.opened) { - // emit play event to start loading tiles in Editor mode + if (AFRAME.INSPECTOR?.opened) { google3dElement.addEventListener( 'loaded', () => { + AFRAME.INSPECTOR.emit('entitycreated', google3dElement); + // emit play event to start loading tiles in Editor mode google3dElement.play(); }, { once: true } @@ -212,12 +228,23 @@ AFRAME.registerComponent('street-geo', { osm3dBuildingElement.classList.add('autocreated'); osm3dBuildingElement.setAttribute('data-ignore-raycaster', ''); - if (AFRAME.INSPECTOR && AFRAME.INSPECTOR.opened) { - // emit play event to start loading tiles in Editor mode + if (AFRAME.INSPECTOR?.opened) { osm3dElement.addEventListener( 'loaded', () => { + AFRAME.INSPECTOR.emit('entitycreated', osm3dElement); + // emit play event to start loading tiles in Editor mode osm3dElement.play(); + }, + { once: true } + ); + } + if (AFRAME.INSPECTOR?.opened) { + osm3dBuildingElement.addEventListener( + 'loaded', + () => { + AFRAME.INSPECTOR.emit('entitycreated', osm3dBuildingElement); + // emit play event to start loading tiles in Editor mode osm3dBuildingElement.play(); }, { once: true } diff --git a/src/editor/index.js b/src/editor/index.js index cea086c07..5dab0852d 100644 --- a/src/editor/index.js +++ b/src/editor/index.js @@ -20,7 +20,9 @@ function Inspector() { this.history = new History(); this.isFirstOpen = true; this.modules = {}; - this.on = Events.on; + this.on = Events.on.bind(Events); + this.emit = Events.emit.bind(Events); + this.off = Events.off.bind(Events); this.opened = false; // Wait for stuff. From b036d0faa309c52881b1f2725cb314d981eaff00 Mon Sep 17 00:00:00 2001 From: Vincent Fretin Date: Wed, 28 Aug 2024 15:17:09 +0200 Subject: [PATCH 32/49] recreate with the same id when redo entiycreate command --- src/editor/lib/commands/EntityCreateCommand.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/editor/lib/commands/EntityCreateCommand.js b/src/editor/lib/commands/EntityCreateCommand.js index 9ae7ab093..135b396f8 100644 --- a/src/editor/lib/commands/EntityCreateCommand.js +++ b/src/editor/lib/commands/EntityCreateCommand.js @@ -19,12 +19,17 @@ export class EntityCreateCommand extends Command { } execute() { - const definition = this.definition; + let definition = this.definition; const callback = (entity) => { this.editor.selectEntity(entity); }; const parentEl = this.definition.parentEl ?? document.querySelector('#street-container'); + // If we undo and redo, use the previous id so next redo actions (for example entityupdate to move the position) works correctly + if (this.entityId) { + definition = { ...this.definition, id: this.entityId }; + } + const entity = createEntity(definition, callback, parentEl); this.entityId = entity.id; return entity; From b3da692107c877aa60b6ba7e5a56acd018581d48 Mon Sep 17 00:00:00 2001 From: Vincent Fretin Date: Wed, 28 Aug 2024 15:35:48 +0200 Subject: [PATCH 33/49] update jsdoc for updateEntity function --- src/editor/lib/entity.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/editor/lib/entity.js b/src/editor/lib/entity.js index acf730c56..d175c3897 100644 --- a/src/editor/lib/entity.js +++ b/src/editor/lib/entity.js @@ -8,8 +8,8 @@ import { equal } from './utils'; * * @param {Element} entity - Entity to modify. * @param {string} component - component name - * @param {string} property - property name, use empty string if component is not a multi-property - * @param {string|number} value - New value. + * @param {string} property - property name, use empty string if component is single property or if value is an object + * @param {string|number|object} value - New value. */ export function updateEntity(entity, component, property, value) { if (property) { From 5ac90b7647d7c31ade0438074fb2fb655b87e5ad Mon Sep 17 00:00:00 2001 From: Vincent Fretin Date: Wed, 28 Aug 2024 20:52:38 +0200 Subject: [PATCH 34/49] remove exposed off/on/emit on inspector, remove entityclone, entitycreated, entityremoved, updatescenegraph events, use child-attached/child-detached events to update SceneGraph --- src/components/street-geo.js | 18 ------ .../AddLayerPanel/AddLayerPanel.component.jsx | 13 +++-- .../ScenesModal/ScenesModal.component.jsx | 2 - .../components/scenegraph/SceneGraph.js | 58 ++++++++++++------- src/editor/components/scenegraph/Toolbar.js | 1 - .../lib/commands/EntityCreateCommand.js | 2 - .../lib/commands/EntityRemoveCommand.js | 3 - src/editor/lib/entity.js | 2 - 8 files changed, 45 insertions(+), 54 deletions(-) diff --git a/src/components/street-geo.js b/src/components/street-geo.js index 026b23698..71f44d11e 100644 --- a/src/components/street-geo.js +++ b/src/components/street-geo.js @@ -65,15 +65,9 @@ AFRAME.registerComponent('street-geo', { } else if (this[mapType] && data.maps !== mapType) { // remove element from DOM and from this object this.el.removeChild(this[mapType]); - if (AFRAME.INSPECTOR?.opened) { - AFRAME.INSPECTOR.emit('entityremoved', this[mapType]); - } this[mapType] = null; if (mapType === 'osm3d') { this.el.removeChild(this['osm3dBuilding']); - if (AFRAME.INSPECTOR?.opened) { - AFRAME.INSPECTOR.emit('entityremoved', this['osm3dBuilding']); - } } } } @@ -103,15 +97,6 @@ AFRAME.registerComponent('street-geo', { }); mapbox2dElement.classList.add('autocreated'); mapbox2dElement.setAttribute('data-ignore-raycaster', ''); - if (AFRAME.INSPECTOR?.opened) { - mapbox2dElement.addEventListener( - 'loaded', - () => { - AFRAME.INSPECTOR.emit('entitycreated', mapbox2dElement); - }, - { once: true } - ); - } el.appendChild(mapbox2dElement); this['mapbox2d'] = mapbox2dElement; document.getElementById('map-copyright').textContent = 'MapBox'; @@ -148,7 +133,6 @@ AFRAME.registerComponent('street-geo', { google3dElement.addEventListener( 'loaded', () => { - AFRAME.INSPECTOR.emit('entitycreated', google3dElement); // emit play event to start loading tiles in Editor mode google3dElement.play(); }, @@ -232,7 +216,6 @@ AFRAME.registerComponent('street-geo', { osm3dElement.addEventListener( 'loaded', () => { - AFRAME.INSPECTOR.emit('entitycreated', osm3dElement); // emit play event to start loading tiles in Editor mode osm3dElement.play(); }, @@ -243,7 +226,6 @@ AFRAME.registerComponent('street-geo', { osm3dBuildingElement.addEventListener( 'loaded', () => { - AFRAME.INSPECTOR.emit('entitycreated', osm3dBuildingElement); // emit play event to start loading tiles in Editor mode osm3dBuildingElement.play(); }, diff --git a/src/editor/components/components/AddLayerPanel/AddLayerPanel.component.jsx b/src/editor/components/components/AddLayerPanel/AddLayerPanel.component.jsx index bf772b609..beb7cd9e0 100644 --- a/src/editor/components/components/AddLayerPanel/AddLayerPanel.component.jsx +++ b/src/editor/components/components/AddLayerPanel/AddLayerPanel.component.jsx @@ -211,15 +211,16 @@ const cardMouseEnter = (mixinId) => { previewEntity.setAttribute('id', 'previewEntity'); AFRAME.scenes[0].appendChild(previewEntity); const dropCursorEntity = document.createElement('a-entity'); + dropCursorEntity.classList.add('hideFromSceneGraph'); dropCursorEntity.innerHTML = ` - - + - - - - + + + `; previewEntity.appendChild(dropCursorEntity); } diff --git a/src/editor/components/modals/ScenesModal/ScenesModal.component.jsx b/src/editor/components/modals/ScenesModal/ScenesModal.component.jsx index 435b0738c..2c812439e 100644 --- a/src/editor/components/modals/ScenesModal/ScenesModal.component.jsx +++ b/src/editor/components/modals/ScenesModal/ScenesModal.component.jsx @@ -9,7 +9,6 @@ import { inputStreetmix } from '../../../lib/toolbar'; import { getCommunityScenes, getUserScenes } from '../../../api/scene'; -import Events from '../../../lib/Events'; import { Load24Icon, Loader, Upload24Icon } from '../../../icons'; import { signIn } from '../../../api'; import posthog from 'posthog-js'; @@ -68,7 +67,6 @@ const ScenesModal = ({ isOpen, onClose, initialTab = 'owner', delay }) => { AFRAME.scenes[0].setAttribute('metadata', 'sceneId', sceneId); AFRAME.scenes[0].setAttribute('metadata', 'sceneTitle', sceneTitle); - Events.emit('updatescenegraph'); STREET.notify.successMessage('Scene loaded from 3DStreet Cloud.'); onClose(); } diff --git a/src/editor/components/scenegraph/SceneGraph.js b/src/editor/components/scenegraph/SceneGraph.js index d25d9cfef..089070e45 100644 --- a/src/editor/components/scenegraph/SceneGraph.js +++ b/src/editor/components/scenegraph/SceneGraph.js @@ -1,5 +1,6 @@ /* eslint-disable no-unused-vars, react/no-danger */ import classNames from 'classnames'; +import debounce from 'lodash-es/debounce'; import PropTypes from 'prop-types'; import React from 'react'; import Events from '../../lib/Events'; @@ -9,7 +10,7 @@ import { LayersIcon, ArrowLeftIcon } from '../../icons'; import { getEntityDisplayName } from '../../lib/entity'; import posthog from 'posthog-js'; -const HIDDEN_CLASSES = ['teleportRay', 'hitEntity']; +const HIDDEN_CLASSES = ['teleportRay', 'hitEntity', 'hideFromSceneGraph']; const HIDDEN_IDS = ['dropPlane', 'previewEntity']; export default class SceneGraph extends React.Component { @@ -31,6 +32,11 @@ export default class SceneGraph extends React.Component { leftBarHide: false, selectedIndex: -1 }; + + this.rebuildEntityOptions = debounce( + this.rebuildEntityOptions.bind(this), + 0 + ); } onMixinUpdate = (detail) => { @@ -39,23 +45,31 @@ export default class SceneGraph extends React.Component { } }; + onChildAttachedDetached = (event) => { + if (this.includeInSceneGraph(event.detail.el)) { + this.rebuildEntityOptions(); + } + }; + componentDidMount() { this.rebuildEntityOptions(); - Events.on('updatescenegraph', this.rebuildEntityOptions); Events.on('entityidchange', this.rebuildEntityOptions); - Events.on('entitycreated', this.rebuildEntityOptions); - Events.on('entityclone', this.rebuildEntityOptions); - Events.on('entityremoved', this.rebuildEntityOptions); Events.on('entityupdate', this.onMixinUpdate); + document.addEventListener('child-attached', this.onChildAttachedDetached); + document.addEventListener('child-detached', this.onChildAttachedDetached); } componentWillUnmount() { - Events.off('updatescenegraph', this.rebuildEntityOptions); Events.off('entityidchange', this.rebuildEntityOptions); - Events.off('entitycreated', this.rebuildEntityOptions); - Events.off('entityclone', this.rebuildEntityOptions); - Events.off('entityremoved', this.rebuildEntityOptions); Events.off('entityupdate', this.onMixinUpdate); + document.removeEventListener( + 'child-attached', + this.onChildAttachedDetached + ); + document.removeEventListener( + 'child-detached', + this.onChildAttachedDetached + ); } /** @@ -92,10 +106,22 @@ export default class SceneGraph extends React.Component { } }; + includeInSceneGraph = (element) => { + return !( + element.dataset.isInspector || + !element.isEntity || + element.isInspector || + 'aframeInspector' in element.dataset || + HIDDEN_CLASSES.includes(element.className) || + HIDDEN_IDS.includes(element.id) + ); + }; + rebuildEntityOptions = () => { + console.log('rebuilding entity options'); const entities = []; - function treeIterate(element, depth) { + const treeIterate = (element, depth) => { if (!element) { return; } @@ -104,15 +130,7 @@ export default class SceneGraph extends React.Component { for (let i = 0; i < element.children.length; i++) { let entity = element.children[i]; - if ( - entity.dataset.isInspector || - !entity.isEntity || - entity.isInspector || - 'aframeInspector' in entity.dataset || - HIDDEN_CLASSES.includes(entity.className) || - HIDDEN_IDS.includes(entity.id) || - (depth === 1 && !entity.id) - ) { + if (!this.includeInSceneGraph(entity) || (depth === 1 && !entity.id)) { continue; } @@ -124,7 +142,7 @@ export default class SceneGraph extends React.Component { treeIterate(entity, depth); } - } + }; const layers = this.props.scene.children; const orderedLayers = []; diff --git a/src/editor/components/scenegraph/Toolbar.js b/src/editor/components/scenegraph/Toolbar.js index f99913099..8342549ac 100644 --- a/src/editor/components/scenegraph/Toolbar.js +++ b/src/editor/components/scenegraph/Toolbar.js @@ -163,7 +163,6 @@ export default class Toolbar extends Component { newHandler = () => { AFRAME.INSPECTOR.selectEntity(null); STREET.utils.newScene(); - Events.emit('updatescenegraph'); }; cloudSaveHandler = async ({ doSaveAs = false }) => { diff --git a/src/editor/lib/commands/EntityCreateCommand.js b/src/editor/lib/commands/EntityCreateCommand.js index 135b396f8..c6f836cd3 100644 --- a/src/editor/lib/commands/EntityCreateCommand.js +++ b/src/editor/lib/commands/EntityCreateCommand.js @@ -1,4 +1,3 @@ -import Events from '../Events'; import { Command } from '../command.js'; import { createEntity } from '../entity.js'; @@ -40,7 +39,6 @@ export class EntityCreateCommand extends Command { if (entity) { this.editor.selectEntity(null); entity.parentNode.removeChild(entity); - Events.emit('entityremoved', entity); } } } diff --git a/src/editor/lib/commands/EntityRemoveCommand.js b/src/editor/lib/commands/EntityRemoveCommand.js index 8abbeadc5..79270e2f4 100644 --- a/src/editor/lib/commands/EntityRemoveCommand.js +++ b/src/editor/lib/commands/EntityRemoveCommand.js @@ -1,4 +1,3 @@ -import Events from '../Events'; import { Command } from '../command.js'; import { findClosestEntity, prepareForSerialization } from '../entity.js'; @@ -25,7 +24,6 @@ export class EntityRemoveCommand extends Command { // Remove entity this.entity.parentNode.removeChild(this.entity); - Events.emit('entityremoved', this.entity); // Replace this.entity by clone this.entity = clone; @@ -42,7 +40,6 @@ export class EntityRemoveCommand extends Command { this.entity.addEventListener( 'loaded', () => { - Events.emit('entitycreated', this.entity); this.editor.selectEntity(this.entity); }, { once: true } diff --git a/src/editor/lib/entity.js b/src/editor/lib/entity.js index d175c3897..5eb92cd4f 100644 --- a/src/editor/lib/entity.js +++ b/src/editor/lib/entity.js @@ -118,7 +118,6 @@ export function cloneEntity(entity) { 'loaded', function () { AFRAME.INSPECTOR.selectEntity(clone); - Events.emit('entityclone', clone); }, { once: true } ); @@ -620,7 +619,6 @@ export function createEntity(definition, cb, parentEl = undefined) { entity.addEventListener( 'loaded', () => { - Events.emit('entitycreated', entity); cb(entity); }, { once: true } From 229bd8cf4a943cd3b8f21040258e30199629696b Mon Sep 17 00:00:00 2001 From: Vincent Fretin Date: Thu, 29 Aug 2024 10:54:33 +0200 Subject: [PATCH 35/49] put back the entitycreated,entityclone,entityremoved events even if we don't use them for now, we may use them later to detect if the user did some changes themself and trigger autosave --- src/editor/lib/commands/EntityCreateCommand.js | 2 ++ src/editor/lib/commands/EntityRemoveCommand.js | 3 +++ src/editor/lib/entity.js | 2 ++ 3 files changed, 7 insertions(+) diff --git a/src/editor/lib/commands/EntityCreateCommand.js b/src/editor/lib/commands/EntityCreateCommand.js index c6f836cd3..135b396f8 100644 --- a/src/editor/lib/commands/EntityCreateCommand.js +++ b/src/editor/lib/commands/EntityCreateCommand.js @@ -1,3 +1,4 @@ +import Events from '../Events'; import { Command } from '../command.js'; import { createEntity } from '../entity.js'; @@ -39,6 +40,7 @@ export class EntityCreateCommand extends Command { if (entity) { this.editor.selectEntity(null); entity.parentNode.removeChild(entity); + Events.emit('entityremoved', entity); } } } diff --git a/src/editor/lib/commands/EntityRemoveCommand.js b/src/editor/lib/commands/EntityRemoveCommand.js index 79270e2f4..8abbeadc5 100644 --- a/src/editor/lib/commands/EntityRemoveCommand.js +++ b/src/editor/lib/commands/EntityRemoveCommand.js @@ -1,3 +1,4 @@ +import Events from '../Events'; import { Command } from '../command.js'; import { findClosestEntity, prepareForSerialization } from '../entity.js'; @@ -24,6 +25,7 @@ export class EntityRemoveCommand extends Command { // Remove entity this.entity.parentNode.removeChild(this.entity); + Events.emit('entityremoved', this.entity); // Replace this.entity by clone this.entity = clone; @@ -40,6 +42,7 @@ export class EntityRemoveCommand extends Command { this.entity.addEventListener( 'loaded', () => { + Events.emit('entitycreated', this.entity); this.editor.selectEntity(this.entity); }, { once: true } diff --git a/src/editor/lib/entity.js b/src/editor/lib/entity.js index 5eb92cd4f..d175c3897 100644 --- a/src/editor/lib/entity.js +++ b/src/editor/lib/entity.js @@ -118,6 +118,7 @@ export function cloneEntity(entity) { 'loaded', function () { AFRAME.INSPECTOR.selectEntity(clone); + Events.emit('entityclone', clone); }, { once: true } ); @@ -619,6 +620,7 @@ export function createEntity(definition, cb, parentEl = undefined) { entity.addEventListener( 'loaded', () => { + Events.emit('entitycreated', entity); cb(entity); }, { once: true } From 94d64c20f52574e485d87a9c11f3c4d27deea3db Mon Sep 17 00:00:00 2001 From: Vincent Fretin Date: Thu, 29 Aug 2024 12:32:24 +0200 Subject: [PATCH 36/49] add entityclone command --- src/editor/lib/commands/EntityCloneCommand.js | 36 +++++++++++++++++++ .../lib/commands/EntityCreateCommand.js | 2 +- .../lib/commands/EntityRemoveCommand.js | 2 +- src/editor/lib/commands/index.js | 6 ++-- src/editor/lib/entity.js | 33 ++++++++++++++--- 5 files changed, 70 insertions(+), 9 deletions(-) create mode 100644 src/editor/lib/commands/EntityCloneCommand.js diff --git a/src/editor/lib/commands/EntityCloneCommand.js b/src/editor/lib/commands/EntityCloneCommand.js new file mode 100644 index 000000000..97cfa922d --- /dev/null +++ b/src/editor/lib/commands/EntityCloneCommand.js @@ -0,0 +1,36 @@ +import Events from '../Events.js'; +import { Command } from '../command.js'; +import { cloneEntityImpl, createUniqueId } from '../entity.js'; + +export class EntityCloneCommand extends Command { + constructor(editor, entity) { + super(editor); + + this.type = 'entityclone'; + this.name = 'Clone Entity'; + this.updatable = false; + if (!entity.id) { + entity.id = createUniqueId(); + } + this.entityIdToClone = entity.id; + this.entityId = null; + } + + execute() { + const entityToClone = document.getElementById(this.entityIdToClone); + if (entityToClone) { + const clone = cloneEntityImpl(entityToClone, this.entityId); + this.entityId = clone.id; + return clone; + } + } + + undo() { + const entity = document.getElementById(this.entityId); + if (entity) { + entity.parentNode.removeChild(entity); + Events.emit('entityremoved', entity); + this.editor.selectEntity(document.getElementById(this.entityIdToClone)); + } + } +} diff --git a/src/editor/lib/commands/EntityCreateCommand.js b/src/editor/lib/commands/EntityCreateCommand.js index 135b396f8..062ff9a3b 100644 --- a/src/editor/lib/commands/EntityCreateCommand.js +++ b/src/editor/lib/commands/EntityCreateCommand.js @@ -38,9 +38,9 @@ export class EntityCreateCommand extends Command { undo() { const entity = document.getElementById(this.entityId); if (entity) { - this.editor.selectEntity(null); entity.parentNode.removeChild(entity); Events.emit('entityremoved', entity); + this.editor.selectEntity(null); } } } diff --git a/src/editor/lib/commands/EntityRemoveCommand.js b/src/editor/lib/commands/EntityRemoveCommand.js index 8abbeadc5..f206d09e4 100644 --- a/src/editor/lib/commands/EntityRemoveCommand.js +++ b/src/editor/lib/commands/EntityRemoveCommand.js @@ -30,7 +30,7 @@ export class EntityRemoveCommand extends Command { // Replace this.entity by clone this.entity = clone; - AFRAME.INSPECTOR.selectEntity(closest); + this.editor.selectEntity(closest); } undo() { diff --git a/src/editor/lib/commands/index.js b/src/editor/lib/commands/index.js index 3c04a2266..2f1a6afbc 100644 --- a/src/editor/lib/commands/index.js +++ b/src/editor/lib/commands/index.js @@ -1,12 +1,14 @@ -import { EntityUpdateCommand } from './EntityUpdateCommand.js'; import { ComponentAddCommand } from './ComponentAddCommand.js'; import { ComponentRemoveCommand } from './ComponentRemoveCommand.js'; +import { EntityCloneCommand } from './EntityCloneCommand.js'; import { EntityCreateCommand } from './EntityCreateCommand.js'; import { EntityRemoveCommand } from './EntityRemoveCommand.js'; +import { EntityUpdateCommand } from './EntityUpdateCommand.js'; export const commandsByType = new Map(); -commandsByType.set('entityupdate', EntityUpdateCommand); commandsByType.set('componentadd', ComponentAddCommand); commandsByType.set('componentremove', ComponentRemoveCommand); +commandsByType.set('entityclone', EntityCloneCommand); commandsByType.set('entitycreate', EntityCreateCommand); commandsByType.set('entityremove', EntityRemoveCommand); +commandsByType.set('entityupdate', EntityUpdateCommand); diff --git a/src/editor/lib/entity.js b/src/editor/lib/entity.js index d175c3897..6fc5f5f58 100644 --- a/src/editor/lib/entity.js +++ b/src/editor/lib/entity.js @@ -108,26 +108,49 @@ function insertAfter(newNode, referenceNode) { /** * Clone an entity, inserting it after the cloned one. - * @param {Element} entity Entity to clone + * @param {Element} entity Entity to clone + * @returns {Element} The clone */ export function cloneEntity(entity) { + return AFRAME.INSPECTOR.execute('entityclone', entity); +} + +/** + * Clone an entity, inserting it after the cloned one. This is the implementation of the entityclone command. + * @param {Element} entity Entity to clone + * @param {string|undefined} newId The new id to use for the clone + * @returns {Element} The clone + */ +export function cloneEntityImpl(entity, newId = undefined) { entity.flushToDOM(); const clone = prepareForSerialization(entity); clone.addEventListener( 'loaded', function () { - AFRAME.INSPECTOR.selectEntity(clone); Events.emit('entityclone', clone); + AFRAME.INSPECTOR.selectEntity(clone); }, { once: true } ); - // Get a valid unique ID for the entity - if (entity.id) { - clone.id = getUniqueId(entity.id); + if (newId) { + clone.id = newId; + } else { + if (entity.id) { + if (entity.id.length === 21) { + // nanoid generated id, create a new one + clone.id = createUniqueId(); + } else { + // Get a valid unique ID for the entity + clone.id = getUniqueId(entity.id); + } + } else { + entity.id = createUniqueId(); + } } insertAfter(clone, entity); + return clone; } /** From 9612ac5965c6e37ed6d9212f941639fd223a05a4 Mon Sep 17 00:00:00 2001 From: Vincent Fretin Date: Thu, 29 Aug 2024 17:08:35 +0200 Subject: [PATCH 37/49] use getDOMAttribute instead of getAttribute to store the old value to be sure we include only properties we set explicitely if we undo and then redo --- src/editor/lib/commands/ComponentRemoveCommand.js | 8 +++++++- src/editor/lib/commands/EntityUpdateCommand.js | 7 +++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/editor/lib/commands/ComponentRemoveCommand.js b/src/editor/lib/commands/ComponentRemoveCommand.js index c4e7c9315..1f5f2fd92 100644 --- a/src/editor/lib/commands/ComponentRemoveCommand.js +++ b/src/editor/lib/commands/ComponentRemoveCommand.js @@ -16,7 +16,13 @@ export class ComponentRemoveCommand extends Command { } this.entityId = entity.id; this.component = payload.component; - this.value = entity.getAttribute(this.component); + + const component = + entity.components[payload.component] ?? + AFRAME.components[payload.component]; + this.value = component.isSingleProperty + ? component.schema.stringify(entity.getAttribute(payload.component)) + : entity.getDOMAttribute(payload.component); } execute() { diff --git a/src/editor/lib/commands/EntityUpdateCommand.js b/src/editor/lib/commands/EntityUpdateCommand.js index 90f9be68c..aadefd4c3 100644 --- a/src/editor/lib/commands/EntityUpdateCommand.js +++ b/src/editor/lib/commands/EntityUpdateCommand.js @@ -49,15 +49,14 @@ export class EntityUpdateCommand extends Command { console.log(this.component, this.oldValue, this.newValue); } } else { - // If component.schema.stringify is undefined then it's multi-property component and value is an object. - this.newValue = component.schema.stringify + this.newValue = component.isSingleProperty ? component.schema.stringify(payload.value) : payload.value; - this.oldValue = component.schema.stringify + this.oldValue = component.isSingleProperty ? component.schema.stringify( payload.entity.getAttribute(payload.component) ) - : payload.entity.getAttribute(payload.component); + : payload.entity.getDOMAttribute(payload.component); if (this.editor.debugUndoRedo) { console.log(this.component, this.oldValue, this.newValue); } From 629fb2d6460cde7e111c30aa95a207aaf21195cf Mon Sep 17 00:00:00 2001 From: Vincent Fretin Date: Thu, 29 Aug 2024 18:06:03 +0200 Subject: [PATCH 38/49] make a copy of the object returned by getDOMAttribute otherwise the object is recycled --- src/editor/lib/commands/ComponentRemoveCommand.js | 2 +- src/editor/lib/commands/EntityUpdateCommand.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/editor/lib/commands/ComponentRemoveCommand.js b/src/editor/lib/commands/ComponentRemoveCommand.js index 1f5f2fd92..18f367172 100644 --- a/src/editor/lib/commands/ComponentRemoveCommand.js +++ b/src/editor/lib/commands/ComponentRemoveCommand.js @@ -22,7 +22,7 @@ export class ComponentRemoveCommand extends Command { AFRAME.components[payload.component]; this.value = component.isSingleProperty ? component.schema.stringify(entity.getAttribute(payload.component)) - : entity.getDOMAttribute(payload.component); + : structuredClone(entity.getDOMAttribute(payload.component)); } execute() { diff --git a/src/editor/lib/commands/EntityUpdateCommand.js b/src/editor/lib/commands/EntityUpdateCommand.js index aadefd4c3..32a922e95 100644 --- a/src/editor/lib/commands/EntityUpdateCommand.js +++ b/src/editor/lib/commands/EntityUpdateCommand.js @@ -56,7 +56,7 @@ export class EntityUpdateCommand extends Command { ? component.schema.stringify( payload.entity.getAttribute(payload.component) ) - : payload.entity.getDOMAttribute(payload.component); + : structuredClone(payload.entity.getDOMAttribute(payload.component)); if (this.editor.debugUndoRedo) { console.log(this.component, this.oldValue, this.newValue); } From 6bf2daa757ee021fee3f8600d74d9ed0f9c8bafc Mon Sep 17 00:00:00 2001 From: Vincent Fretin Date: Fri, 30 Aug 2024 09:24:35 +0200 Subject: [PATCH 39/49] really remove exposed off/on/emit on inspector --- src/editor/index.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/editor/index.js b/src/editor/index.js index 5dab0852d..1fa62f376 100644 --- a/src/editor/index.js +++ b/src/editor/index.js @@ -20,9 +20,6 @@ function Inspector() { this.history = new History(); this.isFirstOpen = true; this.modules = {}; - this.on = Events.on.bind(Events); - this.emit = Events.emit.bind(Events); - this.off = Events.off.bind(Events); this.opened = false; // Wait for stuff. From 1519c5085904d1a2820b5ec37f7f8ed910fed54f Mon Sep 17 00:00:00 2001 From: Vincent Fretin Date: Fri, 30 Aug 2024 09:25:38 +0200 Subject: [PATCH 40/49] add config file to set defaultParent config --- src/editor/index.js | 2 ++ src/editor/lib/commands/EntityCreateCommand.js | 3 ++- src/editor/lib/config.js | 6 ++++++ src/editor/lib/entity.js | 4 +++- 4 files changed, 13 insertions(+), 2 deletions(-) create mode 100644 src/editor/lib/config.js diff --git a/src/editor/index.js b/src/editor/index.js index 1fa62f376..ba0bbdf0d 100644 --- a/src/editor/index.js +++ b/src/editor/index.js @@ -5,6 +5,7 @@ import { AuthProvider, GeoProvider } from './contexts'; import Events from './lib/Events'; import { AssetsLoader } from './lib/assetsLoader'; import { initCameras } from './lib/cameras'; +import { Config } from './lib/config'; import { History } from './lib/history'; import { Shortcuts } from './lib/shortcuts'; import { Viewport } from './lib/viewport'; @@ -17,6 +18,7 @@ import { commandsByType } from './lib/commands/index.js'; function Inspector() { this.assetsLoader = new AssetsLoader(); this.exporters = { gltf: new GLTFExporter() }; + this.config = new Config(); this.history = new History(); this.isFirstOpen = true; this.modules = {}; diff --git a/src/editor/lib/commands/EntityCreateCommand.js b/src/editor/lib/commands/EntityCreateCommand.js index 062ff9a3b..6253c54ab 100644 --- a/src/editor/lib/commands/EntityCreateCommand.js +++ b/src/editor/lib/commands/EntityCreateCommand.js @@ -24,7 +24,8 @@ export class EntityCreateCommand extends Command { this.editor.selectEntity(entity); }; const parentEl = - this.definition.parentEl ?? document.querySelector('#street-container'); + this.definition.parentEl ?? + document.querySelector(this.editor.config.defaultParent); // If we undo and redo, use the previous id so next redo actions (for example entityupdate to move the position) works correctly if (this.entityId) { definition = { ...this.definition, id: this.entityId }; diff --git a/src/editor/lib/config.js b/src/editor/lib/config.js new file mode 100644 index 000000000..7b6025e46 --- /dev/null +++ b/src/editor/lib/config.js @@ -0,0 +1,6 @@ +export function Config() { + return { + // Default parent to add new elements to + defaultParent: '#street-container' + }; +} diff --git a/src/editor/lib/entity.js b/src/editor/lib/entity.js index 6fc5f5f58..937570f89 100644 --- a/src/editor/lib/entity.js +++ b/src/editor/lib/entity.js @@ -652,7 +652,9 @@ export function createEntity(definition, cb, parentEl = undefined) { if (parentEl) { parentEl.appendChild(entity); } else { - document.getElementById('street-container').appendChild(entity); + document + .querySelector(AFRAME.INSPECTOR.config.defaultParent) + .appendChild(entity); } return entity; From 1f07920caae295a56a0f00925659d2f745cc9295 Mon Sep 17 00:00:00 2001 From: Vincent Fretin Date: Fri, 30 Aug 2024 09:28:06 +0200 Subject: [PATCH 41/49] move debugUndoRedo to config --- src/editor/index.js | 1 - src/editor/lib/commands/EntityUpdateCommand.js | 8 ++++---- src/editor/lib/config.js | 2 ++ 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/editor/index.js b/src/editor/index.js index ba0bbdf0d..59a02f91c 100644 --- a/src/editor/index.js +++ b/src/editor/index.js @@ -90,7 +90,6 @@ Inspector.prototype = { this.sceneHelpers.userData.source = 'INSPECTOR'; this.sceneHelpers.visible = true; this.inspectorActive = false; - this.debugUndoRedo = false; this.viewport = new Viewport(this); diff --git a/src/editor/lib/commands/EntityUpdateCommand.js b/src/editor/lib/commands/EntityUpdateCommand.js index 32a922e95..5cd28eb6b 100644 --- a/src/editor/lib/commands/EntityUpdateCommand.js +++ b/src/editor/lib/commands/EntityUpdateCommand.js @@ -45,7 +45,7 @@ export class EntityUpdateCommand extends Command { payload.property ]; } - if (this.editor.debugUndoRedo) { + if (this.editor.config.debugUndoRedo) { console.log(this.component, this.oldValue, this.newValue); } } else { @@ -57,7 +57,7 @@ export class EntityUpdateCommand extends Command { payload.entity.getAttribute(payload.component) ) : structuredClone(payload.entity.getDOMAttribute(payload.component)); - if (this.editor.debugUndoRedo) { + if (this.editor.config.debugUndoRedo) { console.log(this.component, this.oldValue, this.newValue); } } @@ -67,7 +67,7 @@ export class EntityUpdateCommand extends Command { execute() { const entity = document.getElementById(this.entityId); if (entity) { - if (this.editor.debugUndoRedo) { + if (this.editor.config.debugUndoRedo) { console.log( 'execute', entity, @@ -92,7 +92,7 @@ export class EntityUpdateCommand extends Command { } update(command) { - if (this.editor.debugUndoRedo) { + if (this.editor.config.debugUndoRedo) { console.log('update', command); } this.newValue = command.newValue; diff --git a/src/editor/lib/config.js b/src/editor/lib/config.js index 7b6025e46..a50a9efd5 100644 --- a/src/editor/lib/config.js +++ b/src/editor/lib/config.js @@ -1,5 +1,7 @@ export function Config() { return { + // Debug undo/redo + debugUndoRedo: false, // Default parent to add new elements to defaultParent: '#street-container' }; From 3e512c44592af0931984354e7b06b0c0ecc236d9 Mon Sep 17 00:00:00 2001 From: Vincent Fretin Date: Fri, 30 Aug 2024 09:46:22 +0200 Subject: [PATCH 42/49] simplify condition --- .../AddLayerPanel/createLayerFunctions.js | 33 ++++++------------- .../modals/GeoModal/GeoModal.component.jsx | 31 +++++++---------- 2 files changed, 22 insertions(+), 42 deletions(-) diff --git a/src/editor/components/components/AddLayerPanel/createLayerFunctions.js b/src/editor/components/components/AddLayerPanel/createLayerFunctions.js index 770085805..2c04f3ee3 100644 --- a/src/editor/components/components/AddLayerPanel/createLayerFunctions.js +++ b/src/editor/components/components/AddLayerPanel/createLayerFunctions.js @@ -140,29 +140,16 @@ export function create3DTiles() { ellipsoidalHeight = parseFloat(streetGeo['ellipsoidalHeight']) || 0; } - if (streetGeo) { - AFRAME.INSPECTOR.execute('entityupdate', { - entity: geoLayer, - component: 'street-geo', - value: { - latitude: latitude, - longitude: longitude, - ellipsoidalHeight: ellipsoidalHeight, - maps: 'google3d' - } - }); - } else { - AFRAME.INSPECTOR.execute('componentadd', { - entity: geoLayer, - component: 'street-geo', - value: { - latitude: latitude, - longitude: longitude, - ellipsoidalHeight: ellipsoidalHeight, - maps: 'google3d' - } - }); - } + AFRAME.INSPECTOR.execute(streetGeo ? 'entityupdate' : 'componentadd', { + entity: geoLayer, + component: 'street-geo', + value: { + latitude: latitude, + longitude: longitude, + ellipsoidalHeight: ellipsoidalHeight, + maps: 'google3d' + } + }); }; if (AFRAME.components['loader-3dtiles']) { diff --git a/src/editor/components/modals/GeoModal/GeoModal.component.jsx b/src/editor/components/modals/GeoModal/GeoModal.component.jsx index fc07a53f7..03e7177f0 100644 --- a/src/editor/components/modals/GeoModal/GeoModal.component.jsx +++ b/src/editor/components/modals/GeoModal/GeoModal.component.jsx @@ -136,27 +136,20 @@ const GeoModal = ({ isOpen, onClose }) => { console.log(`elevation: ${data.ellipsoidalHeight}`); const geoLayer = document.getElementById('reference-layers'); - const value = { - latitude: latitude, - longitude: longitude, - ellipsoidalHeight: data.ellipsoidalHeight, - orthometricHeight: data.orthometricHeight, - geoidHeight: data.geoidHeight - }; - - if (geoLayer.hasAttribute('street-geo')) { - AFRAME.INSPECTOR.execute('entityupdate', { - entity: geoLayer, - component: 'street-geo', - value - }); - } else { - AFRAME.INSPECTOR.execute('componentadd', { + AFRAME.INSPECTOR.execute( + geoLayer.hasAttribute('street-geo') ? 'entityupdate' : 'componentadd', + { entity: geoLayer, component: 'street-geo', - value - }); - } + value: { + latitude: latitude, + longitude: longitude, + ellipsoidalHeight: data.ellipsoidalHeight, + orthometricHeight: data.orthometricHeight, + geoidHeight: data.geoidHeight + } + } + ); } setIsWorking(false); From 39e7a28ee469b206a89e2c408a3860c72899a0de Mon Sep 17 00:00:00 2001 From: Vincent Fretin Date: Wed, 28 Aug 2024 10:24:31 +0200 Subject: [PATCH 43/49] avoid importing from .component.jsx files --- src/editor/components/modals/GeoModal/GeoModal.component.jsx | 4 ++-- src/editor/components/scenegraph/Toolbar.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/editor/components/modals/GeoModal/GeoModal.component.jsx b/src/editor/components/modals/GeoModal/GeoModal.component.jsx index 03e7177f0..4b651f9da 100644 --- a/src/editor/components/modals/GeoModal/GeoModal.component.jsx +++ b/src/editor/components/modals/GeoModal/GeoModal.component.jsx @@ -1,5 +1,5 @@ import { useState, useCallback, useEffect } from 'react'; -import { SavingModal } from '../SavingModal/SavingModal.component.jsx'; +import { SavingModal } from '../SavingModal'; import styles from './GeoModal.module.scss'; import { Mangnifier20Icon, Save24Icon, QR32Icon } from '../../../icons'; @@ -16,7 +16,7 @@ import { } from '@react-google-maps/api'; import GeoImg from '../../../../../ui_assets/geo.png'; import { roundCoord } from '../../../../../src/utils.js'; -import { QrCode } from '../../components/QrCode/QrCode.component.jsx'; +import { QrCode } from '../../components/QrCode'; const GeoModal = ({ isOpen, onClose }) => { const { isLoaded } = useJsApiLoader({ diff --git a/src/editor/components/scenegraph/Toolbar.js b/src/editor/components/scenegraph/Toolbar.js index 8342549ac..b05a15acf 100644 --- a/src/editor/components/scenegraph/Toolbar.js +++ b/src/editor/components/scenegraph/Toolbar.js @@ -19,7 +19,7 @@ import { SavingModal } from '../modals/SavingModal'; import { uploadThumbnailImage } from '../modals/ScreenshotModal/ScreenshotModal.component.jsx'; import { sendMetric } from '../../services/ga.js'; import posthog from 'posthog-js'; -import { UndoRedo } from '../components/UndoRedo/UndoRedo.component.jsx'; +import { UndoRedo } from '../components/UndoRedo'; // const LOCALSTORAGE_MOCAP_UI = "aframeinspectormocapuienabled"; function filterHelpers(scene, visible) { From 999d56677c23bb48d1fe3b1f21a4d839c7bc4c1c Mon Sep 17 00:00:00 2001 From: Vincent Fretin Date: Fri, 30 Aug 2024 09:52:46 +0200 Subject: [PATCH 44/49] simplify condition --- .../AddLayerPanel/createLayerFunctions.js | 30 ++++++------------- 1 file changed, 9 insertions(+), 21 deletions(-) diff --git a/src/editor/components/components/AddLayerPanel/createLayerFunctions.js b/src/editor/components/components/AddLayerPanel/createLayerFunctions.js index 2c04f3ee3..a664a90a3 100644 --- a/src/editor/components/components/AddLayerPanel/createLayerFunctions.js +++ b/src/editor/components/components/AddLayerPanel/createLayerFunctions.js @@ -37,27 +37,15 @@ export function createMapbox() { longitude = roundCoord(parseFloat(streetGeo['longitude'])); } - if (streetGeo) { - AFRAME.INSPECTOR.execute('entityupdate', { - entity: geoLayer, - component: 'street-geo', - value: { - latitude: latitude, - longitude: longitude, - maps: 'mapbox2d' - } - }); - } else { - AFRAME.INSPECTOR.execute('componentadd', { - entity: geoLayer, - component: 'street-geo', - value: { - latitude: latitude, - longitude: longitude, - maps: 'mapbox2d' - } - }); - } + AFRAME.INSPECTOR.execute(streetGeo ? 'entityupdate' : 'componentadd', { + entity: geoLayer, + component: 'street-geo', + value: { + latitude: latitude, + longitude: longitude, + maps: 'mapbox2d' + } + }); } export function createStreetmixStreet(position, streetmixURL, hideBuildings) { From 10486aa8b67dc26747a263bc644af84eba9c85a4 Mon Sep 17 00:00:00 2001 From: Vincent Fretin Date: Fri, 30 Aug 2024 09:55:41 +0200 Subject: [PATCH 45/49] remove comment, we put back createEntity with doc in entity.js --- src/editor/lib/commands/EntityCreateCommand.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/editor/lib/commands/EntityCreateCommand.js b/src/editor/lib/commands/EntityCreateCommand.js index 6253c54ab..c8ceea4b0 100644 --- a/src/editor/lib/commands/EntityCreateCommand.js +++ b/src/editor/lib/commands/EntityCreateCommand.js @@ -2,12 +2,6 @@ import Events from '../Events'; import { Command } from '../command.js'; import { createEntity } from '../entity.js'; -/** - * Helper function to add a new entity with a list of components - * @param {object} definition Entity definition to add, only components is required: - * {element: 'a-entity', id: "hbiuSdYL2", class: "box", components: {geometry: 'primitive:box'}} - * @return {Element} Entity created - */ export class EntityCreateCommand extends Command { constructor(editor, definition) { super(editor); From 0abebfcb6c3bbbe6645d782b7cb88214add6752a Mon Sep 17 00:00:00 2001 From: Vincent Fretin Date: Fri, 30 Aug 2024 14:51:01 +0200 Subject: [PATCH 46/49] add empty line --- src/editor/lib/shortcuts.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/editor/lib/shortcuts.js b/src/editor/lib/shortcuts.js index 41ed3fb7f..6f89b6a4f 100644 --- a/src/editor/lib/shortcuts.js +++ b/src/editor/lib/shortcuts.js @@ -115,6 +115,7 @@ export const Shortcuts = { if (!shouldCaptureKeyEvent(event) || !AFRAME.INSPECTOR.opened) { return; } + if ( (event.ctrlKey && os !== 'macos') || (event.metaKey && os === 'macos') From 38b517565865024a53f39c54c82e32479b6d6fe5 Mon Sep 17 00:00:00 2001 From: Vincent Fretin Date: Fri, 30 Aug 2024 14:51:30 +0200 Subject: [PATCH 47/49] add comment --- src/editor/lib/utils.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/editor/lib/utils.js b/src/editor/lib/utils.js index bf193670a..074c13a47 100644 --- a/src/editor/lib/utils.js +++ b/src/editor/lib/utils.js @@ -105,6 +105,9 @@ export function saveBlob(blob, filename) { link.remove(); } +// Compares 2 vector objects up to size 4 +// Expect v1 and v2 to take format {x: number, y: number, z: number, w:number} +// Smaller vectors (ie. vec2) should work as well since their z & w vals will be the same (undefined) export function areVectorsEqual(v1, v2) { return ( Object.is(v1.x, v2.x) && From 798c226ab4c35c7ea653a21dda9d256f3b5de0a4 Mon Sep 17 00:00:00 2001 From: Vincent Fretin Date: Fri, 30 Aug 2024 18:59:42 +0200 Subject: [PATCH 48/49] remove dead code --- src/editor/index.js | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/editor/index.js b/src/editor/index.js index 59a02f91c..973470674 100644 --- a/src/editor/index.js +++ b/src/editor/index.js @@ -211,13 +211,6 @@ Inspector.prototype = { this.sceneHelpers.visible = this.inspectorActive; }); - Events.on('entitycreate', (definition) => { - console.warn( - 'You should call AFRAME.INSPECTOR.execute("entitycreate", definition) instead of Events.emit("entitycreate", definition)' - ); - this.execute('entitycreate', definition); - }); - this.sceneEl.addEventListener('newScene', () => { this.history.clear(); }); From e38cfd4ffe2645398c039a3c3f3cb66896934964 Mon Sep 17 00:00:00 2001 From: Vincent Fretin Date: Fri, 30 Aug 2024 19:11:46 +0200 Subject: [PATCH 49/49] remove console.log --- src/editor/components/scenegraph/SceneGraph.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/editor/components/scenegraph/SceneGraph.js b/src/editor/components/scenegraph/SceneGraph.js index 089070e45..b58683435 100644 --- a/src/editor/components/scenegraph/SceneGraph.js +++ b/src/editor/components/scenegraph/SceneGraph.js @@ -118,7 +118,6 @@ export default class SceneGraph extends React.Component { }; rebuildEntityOptions = () => { - console.log('rebuilding entity options'); const entities = []; const treeIterate = (element, depth) => {