Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion example/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<script src="js/aframe.min.js"></script>
</head>
<body>
<a-scene debug stats>
<a-scene debug="true" stats>
<a-assets>
<a-mixin id="blue" material="color: #4CC3D9"></a-mixin>
<a-mixin id="blueBox" geometry="primitive: box; depth: 2; height: 5; width: 1" material="color: #4CC3D9"></a-mixin>
Expand Down
110 changes: 109 additions & 1 deletion src/actions/entity.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* global AFRAME */
/* global AFRAME editor */
var Events = require('../lib/Events.js');

var components = AFRAME.components;
Expand Down Expand Up @@ -38,3 +38,111 @@ export function updateEntity (entity, componentName, propertyName, value) {
}
Events.emit('objectChanged', entity.object3D);
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can run linter

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👼 sorry for that :) done

/**
* Remove an entity
* @param {Element} entity Entity to remove.
* @param {boolean} force (Optional) If true it won't ask for confirmation
*/
export function removeEntity (entity, force) {
if (entity) {
if (force === true || confirm('Do you really want to remove the entity: `' + (entity.id || entity.tagName) + '`')) {
var closest = findClosestEntity(entity);
entity.parentNode.removeChild(entity);
editor.selectEntity(closest);
}
}
}

function findClosestEntity (entity) {
// First we try to find the after the entity
var nextEntity = entity.nextElementSibling;
while (nextEntity && (!nextEntity.isEntity || nextEntity.isEditor)) {
nextEntity = nextEntity.nextElementSibling;
}

// Return if we found it
if (nextEntity && nextEntity.isEntity && !nextEntity.isEditor) {
return nextEntity;
}
// Otherwise try to find before the entity
var prevEntity = entity.previousElementSibling;
while (prevEntity && (!prevEntity.isEntity || prevEntity.isEditor)) {
prevEntity = prevEntity.previousElementSibling;
}

// Return if we found it
if (prevEntity && prevEntity.isEntity && !prevEntity.isEditor) {
return prevEntity;
}

return null;
}

/**
* Remove the selected entity
* @param {boolean} force (Optional) If true it won't ask for confirmation
*/
export function removeSelectedEntity (force) {
removeEntity(editor.selectedEntity);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we pass in the entity rather than rely on global

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, in fact we have exposed both functions, but on the EditMenu.js I used the following code as the menu doesn't have a reference to the current selected entity:

var menuOptions = {
  'Clone': {group: 'base', callback: cloneSelectedEntity, needsEntity: true},
  'Remove': {group: 'base', callback: removeSelectedEntity, needsEntity: true}
};

Maybe callback could be the one calling removeEntity(editor..') and delete the *selected on entity.js

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, that's fine for now. have the callback pass it

}

/**
* Insert an node after a referenced node.
* @param {Element} newNode Node to insert.
* @param {Element} referenceNode Node used as reference to insert after it.
*/
function insertAfter (newNode, referenceNode) {
referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling);
}

/**
* Clone an entity, inserting it after the cloned one.
* @param {Element} entity Entity to clone
*/
export function cloneEntity (entity) {
var copy = entity.cloneNode(true);
copy.addEventListener('loaded', function (e) {
editor.selectEntity(copy);
});

// Get a valid unique ID for the entity
if (entity.id) {
copy.id = getUniqueId(entity.id);
}
copy.addEventListener('loaded', function () {
Events.emit('sceneModified');
editor.selectEntity(copy);
});
insertAfter(copy, entity);
}

/**
* Clone the selected entity
*/
export function cloneSelectedEntity () {
cloneEntity(editor.selectedEntity);
}

/**
* Detect element's Id collision and returns a valid one
* @param {string} baseId Proposed Id
* @return {string} Valid Id based on the proposed Id
*/
function getUniqueId (baseId) {
if (!document.getElementById(baseId)) {
return baseId;
}

var i = 2;
// If the baseId ends with _#, it extracts the baseId removing the suffix
var groups = baseId.match(/(\w+)-(\d+)/);
if (groups) {
baseId = groups[1];
i = groups[2];
}

while (document.getElementById(baseId + '-' + i)) { i++; }

return baseId + '-' + i;
}
2 changes: 0 additions & 2 deletions src/components/Main.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
/* global editor */
require('../lib/vendor/ga');
const Events = require('../lib/Events.js');
const Editor = require('../lib/editor');

import React from 'react';
import ReactDOM from 'react-dom';
Expand Down
21 changes: 13 additions & 8 deletions src/components/SceneGraph.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react';
import debounce from 'lodash.debounce';

import {removeEntity, cloneEntity} from '../actions/entity';
const Events = require('../lib/Events.js');

const ICONS = {
Expand Down Expand Up @@ -76,6 +77,7 @@ export default class SceneGraph extends React.Component {
if (!found) {
this.setState({value: null, selectedIndex: -1});
}
ga('send', 'event', 'SceneGraph', 'selectEntity');
}

update = e => {
Expand Down Expand Up @@ -109,11 +111,12 @@ export default class SceneGraph extends React.Component {

const pad = '&nbsp;&nbsp;&nbsp;'.repeat(depth);
const label = child.id ? child.id : '&lt;' + child.tagName.toLowerCase() + '&gt;';
const html = pad + label + extra;

options.push({
static: true,
value: child,
html: pad + label + extra
html: html
});

if (child.tagName.toLowerCase() !== 'a-entity') {
Expand Down Expand Up @@ -159,11 +162,6 @@ export default class SceneGraph extends React.Component {
}
}

handleEntityClick (value) {
this.setValue(value);
ga('send', 'event', 'SceneGraph', 'selectEntity');
}

renderOptions () {
var filterText = this.state.filterText.toUpperCase();
return this.state.options
Expand All @@ -176,8 +174,15 @@ export default class SceneGraph extends React.Component {
let className = 'option' + (option.value === this.state.value ? ' active' : '');
return (
<div key={idx} className={className} value={option.value}
dangerouslySetInnerHTML={{__html: option.html}}
onClick={() => {this.handleEntityClick(option.value)}}/>
onClick={() => this.setValue(option.value)}>
<span dangerouslySetInnerHTML={{__html: option.html}}></span>
<span className="icons">
<a onClick={() => cloneEntity(option.value)}
title="Clone entity" className="button fa fa-clone"></a>
<a onClick={event => { event.stopPropagation(); removeEntity(option.value); } }
title="Remove entity" className="button fa fa-trash-o"></a>
</span>
</div>
);
});
}
Expand Down
53 changes: 53 additions & 0 deletions src/components/menu/CreateMenu.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import React from 'react';
var Events = require('../../lib/Events.js');

var primitivesDefinitions = {
'Entity': {group: 'entities', element: 'a-entity', components: {}},

'Box': {group: 'primitives', element: 'a-entity', components: {geometry: 'primitive:box', material: 'color:#f00'}},
'Sphere': {group: 'primitives', element: 'a-entity', components: {geometry: 'primitive:sphere', material: 'color:#ff0'}},
'Cylinder': {group: 'primitives', element: 'a-entity', components: {geometry: 'primitive:cylinder', material: 'color:#00f'}},
'Plane': {group: 'primitives', element: 'a-entity', components: {geometry: 'primitive:plane', material: 'color:#fff'}},
'Torus': {group: 'primitives', element: 'a-entity', components: {geometry: 'primitive:torus', material: 'color:#0f0'}},
'TorusKnot': {group: 'primitives', element: 'a-entity', components: {geometry: 'primitive:torusKnot', material: 'color:#f0f'}},
'Circle': {group: 'primitives', element: 'a-entity', components: {geometry: 'primitive:circle', material: 'color:#f0f'}},
'Ring': {group: 'primitives', element: 'a-entity', components: {geometry: 'primitive:ring', material: 'color:#0ff'}},

'Ambient': {group: 'lights', element: 'a-entity', components: {light: 'type:ambient'}},
'Directional': {group: 'lights', element: 'a-entity', components: {light: 'type:directional'}},
'Hemisphere': {group: 'lights', element: 'a-entity', components: {light: 'type:hemisphere'}},
'Point': {group: 'lights', element: 'a-entity', components: {light: 'type:point'}},
'Spot': {group: 'lights', element: 'a-entity', components: {light: 'type:spot'}},

'Camera': {group: 'cameras', element: 'a-entity', components: {camera: ''}}
};

function createEntity (definition) {
Events.emit('createNewEntity', primitivesDefinitions[definition]);
}

export const CreateMenu = props => {
var prevGroup = null;
var definitions = primitivesDefinitions;
return (
<div className='menu'>
<div className='title'>Create</div>
<div className='options'>
{
Object.keys(definitions).map(definition => {
var output = [];
if (prevGroup === null) {
prevGroup = definitions[definition].group;
} else if (prevGroup !== definitions[definition].group) {
prevGroup = definitions[definition].group;
output.push(<hr/>);
}
output.push(<div className='option' key={definition} value={definition}
onClick={() => createEntity(definition)}>{definition}</div>);
return output;
})
}
</div>
</div>
);
};
33 changes: 33 additions & 0 deletions src/components/menu/EditMenu.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React from 'react';
import {removeSelectedEntity, cloneSelectedEntity} from '../../actions/entity';

var menuOptions = {
'Clone': {group: 'base', callback: cloneSelectedEntity, needsEntity: true},
'Remove': {group: 'base', callback: removeSelectedEntity, needsEntity: true}
};

export const EditMenu = () => {
var prevGroup = null;
return (
<div className='menu'>
<div className='title'>Edit</div>
<div className='options'>
{
Object.keys(menuOptions).map(key => {
var option = menuOptions[key];
var output = [];
if (prevGroup === null) {
prevGroup = option.group;
} else if (prevGroup !== option.group) {
prevGroup = option.group;
output.push(<hr/>);
}
output.push(<div className='option' key={key} value={key}
onClick={() => option.callback()}>{key}</div>);
return output;
})
}
</div>
</div>
);
};
45 changes: 45 additions & 0 deletions src/components/menu/ExportMenu.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import React from 'react';
import Clipboard from 'clipboard';
import {getSceneName, generateHtml} from '../../lib/exporter';

export class ExportMenu extends React.Component {
componentDidMount () {
var clipboard = new Clipboard('[data-action="copy-to-clipboard"]', {
text: trigger => {
return generateHtml();
}
});
clipboard.on('error', e => {
// @todo Show Error on the UI
});
}

saveToHTML () {
var link = document.createElement('a');
link.style.display = 'none';
document.body.appendChild(link);
function save (blob, filename) {
link.href = URL.createObjectURL(blob);
link.download = filename || 'ascene.html';
link.click();
// URL.revokeObjectURL(url); breaks Firefox...
}
function saveString (text, filename) {
save(new Blob([ text ], { type: 'text/plain' }), filename);
}
var sceneName = getSceneName(document.querySelector('a-scene'));
saveString(generateHtml(), sceneName);
}

render () {
return (
<div className='menu'>
<div className='title'>Export</div>
<div className='options'>
<div className='option' onClick={this.saveToHTML}>Save HTML</div>
<div className='option' data-action='copy-to-clipboard'>Copy to clipboard</div>
</div>
</div>
);
}
}
Loading