Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
Added clone and remove entity to the Edit menu
  • Loading branch information
fernandojsg committed Jul 14, 2016
commit d8dc74dc7102f6132842821b1f86d1c6b84b8853
80 changes: 79 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,81 @@ 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) + '`')) {
entity.parentNode.removeChild(entity);
// @todo Select the next entity in the scenegraph
editor.selectEntity(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

}

/**
* 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
copy.id = getUniqueId(entity.id);
//insertAfter(copy, entity);
entity.insertAdjacentHTML('afterend', copy.outerHTML);
}

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

/**
* 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);
Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Member Author

Choose a reason for hiding this comment

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

So should I do something like:

entity.insertAdjacentHTML('afterend', copy.outerHTML);

Right?

}

/**
* 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;
}
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>
);
}
}
142 changes: 9 additions & 133 deletions src/components/menu/Menu.js
Original file line number Diff line number Diff line change
@@ -1,144 +1,20 @@
import React from 'react';
import Clipboard from 'clipboard';
import Events from '../../lib/Events.js';
import {getSceneName, generateHtml} from '../../lib/exporter';

var primitivesDefinitions = {
'Entity': {group: 'entities', components: {}},

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

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

'Camera': {group: 'cameras', components: {camera: ''}}
};

function createEntity (definition) {
Events.emit('createNewEntity', primitivesDefinitions[definition]);
ga('send', 'event', 'CreateMenu', 'createEntity', 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(function(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;
}.bind(this))
}
</div>
</div>
);
};

export const EditMenu = () => {
var prevGroup = null;
var definitions = primitivesDefinitions;
return (
<div className="menu">
<div className="title">Create</div>
<div className="options">
{
Object.keys(definitions).map(function(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}>{definition}</div>);
return output;
}.bind(this))
}
</div>
</div>
);
};
import {CreateMenu} from './CreateMenu';
import {EditMenu} from './EditMenu';
import {ExportMenu} from './ExportMenu';

export class MenuWidget extends React.Component {
componentDidMount() {
var clipboard = new Clipboard('[data-action="copy-to-clipboard"]', {
text: function (trigger) {
return generateHtml();
}
});
clipboard.on('error', function(e) {
console.error('Error while copying to clipboard:', e.action, e.trigger);
});
ga('send', 'event', 'ExportMenu', 'copyHTML');
}

update = e => {
var value = e.target.value;
this.setState({value: value});
if (this.props.onChange)
this.props.onChange(this.props.entity, this.props.componentname, this.props.name, value);
}

saveToHTML() {
var link = document.createElement('a');
link.style.display = 'none';
link.setAttribute('data-aframe-editor', 'download');
document.body.appendChild(link);
function save (blob, filename) {
link.href = URL.createObjectURL(blob);
link.download = filename || 'data.json';
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);
ga('send', 'event', 'ExportMenu', 'exportHTML');
}

render() {
render () {
return (
<div className="Panel" id="menubar">
<div className="menu aframe-logo">
<div className="title">
<div className='panel' id='menubar'>
<div className='menu aframe-logo'>
<div className='title'>
<span style={{color: '#ed3160'}}>A-</span>
<span style={{color: '#fff'}}>Frame</span>
</div>
</div>
<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>
<div className="menu">
<div className="title">Edit</div>
</div>
<ExportMenu/>
<EditMenu/>
<CreateMenu/>
</div>
);
Expand Down
Loading