Skip to content

Commit 81407b6

Browse files
committed
Added clone and remove entity to the Edit menu
1 parent ecba098 commit 81407b6

File tree

7 files changed

+227
-135
lines changed

7 files changed

+227
-135
lines changed

‎src/actions/entity.js‎

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/* global AFRAME */
1+
/* global AFRAME editor */
22
var Events = require('../lib/Events.js');
33

44
var components = AFRAME.components;
@@ -38,3 +38,81 @@ export function updateEntity (entity, componentName, propertyName, value) {
3838
}
3939
Events.emit('objectChanged', entity);
4040
}
41+
42+
/**
43+
* Remove an entity
44+
* @param {Element} entity Entity to remove.
45+
* @param {boolean} force (Optional) If true it won't ask for confirmation
46+
*/
47+
export function removeEntity (entity, force) {
48+
if (entity) {
49+
if (force === true || confirm('Do you really want to remove the entity: `' + (entity.id || entity.tagName) + '`')) {
50+
entity.parentNode.removeChild(entity);
51+
// @todo Select the next entity in the scenegraph
52+
editor.selectEntity(null);
53+
}
54+
}
55+
}
56+
57+
/**
58+
* Remove the selected entity
59+
* @param {boolean} force (Optional) If true it won't ask for confirmation
60+
*/
61+
export function removeSelectedEntity (force) {
62+
removeEntity(editor.selectedEntity);
63+
}
64+
65+
/**
66+
* Clone an entity, inserting it after the cloned one.
67+
* @param {Element} entity Entity to clone
68+
*/
69+
export function cloneEntity (entity) {
70+
var copy = entity.cloneNode(true);
71+
copy.addEventListener('loaded', function (e) {
72+
editor.selectEntity(copy);
73+
});
74+
75+
// Get a valid unique ID for the entity
76+
copy.id = getUniqueId(entity.id);
77+
//insertAfter(copy, entity);
78+
entity.insertAdjacentHTML('afterend', copy.outerHTML);
79+
}
80+
81+
/**
82+
* Clone the selected entity
83+
*/
84+
export function cloneSelectedEntity () {
85+
cloneEntity(editor.selectedEntity);
86+
}
87+
88+
/**
89+
* Insert an node after a referenced node.
90+
* @param {Element} newNode Node to insert.
91+
* @param {Element} referenceNode Node used as reference to insert after it.
92+
*/
93+
function insertAfter (newNode, referenceNode) {
94+
referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling);
95+
}
96+
97+
/**
98+
* Detect element's Id collision and returns a valid one
99+
* @param {string} baseId Proposed Id
100+
* @return {string} Valid Id based on the proposed Id
101+
*/
102+
function getUniqueId (baseId) {
103+
if (!document.getElementById(baseId)) {
104+
return baseId;
105+
}
106+
107+
var i = 2;
108+
// If the baseId ends with _#, it extracts the baseId removing the suffix
109+
var groups = baseId.match(/(\w+)-(\d+)/);
110+
if (groups) {
111+
baseId = groups[1];
112+
i = groups[2];
113+
}
114+
115+
while (document.getElementById(baseId + '-' + i)) { i++; }
116+
117+
return baseId + '-' + i;
118+
}

‎src/components/menu/CreateMenu.js‎

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import React from 'react';
2+
var Events = require('../../lib/Events.js');
3+
4+
var primitivesDefinitions = {
5+
'Entity': {group: 'entities', element: 'a-entity', components: {}},
6+
7+
'Box': {group: 'primitives', element: 'a-entity', components: {geometry: 'primitive:box', material: 'color:#f00'}},
8+
'Sphere': {group: 'primitives', element: 'a-entity', components: {geometry: 'primitive:sphere', material: 'color:#ff0'}},
9+
'Cylinder': {group: 'primitives', element: 'a-entity', components: {geometry: 'primitive:cylinder', material: 'color:#00f'}},
10+
'Plane': {group: 'primitives', element: 'a-entity', components: {geometry: 'primitive:plane', material: 'color:#fff'}},
11+
'Torus': {group: 'primitives', element: 'a-entity', components: {geometry: 'primitive:torus', material: 'color:#0f0'}},
12+
'TorusKnot': {group: 'primitives', element: 'a-entity', components: {geometry: 'primitive:torusKnot', material: 'color:#f0f'}},
13+
'Circle': {group: 'primitives', element: 'a-entity', components: {geometry: 'primitive:circle', material: 'color:#f0f'}},
14+
'Ring': {group: 'primitives', element: 'a-entity', components: {geometry: 'primitive:ring', material: 'color:#0ff'}},
15+
16+
'Ambient': {group: 'lights', element: 'a-entity', components: {light: 'type:ambient'}},
17+
'Directional': {group: 'lights', element: 'a-entity', components: {light: 'type:directional'}},
18+
'Hemisphere': {group: 'lights', element: 'a-entity', components: {light: 'type:hemisphere'}},
19+
'Point': {group: 'lights', element: 'a-entity', components: {light: 'type:point'}},
20+
'Spot': {group: 'lights', element: 'a-entity', components: {light: 'type:spot'}},
21+
22+
'Camera': {group: 'cameras', element: 'a-entity', components: {camera: ''}}
23+
};
24+
25+
function createEntity (definition) {
26+
Events.emit('createNewEntity', primitivesDefinitions[definition]);
27+
}
28+
29+
export const CreateMenu = props => {
30+
var prevGroup = null;
31+
var definitions = primitivesDefinitions;
32+
return (
33+
<div className='menu'>
34+
<div className='title'>Create</div>
35+
<div className='options'>
36+
{
37+
Object.keys(definitions).map(definition => {
38+
var output = [];
39+
if (prevGroup === null) {
40+
prevGroup = definitions[definition].group;
41+
} else if (prevGroup !== definitions[definition].group) {
42+
prevGroup = definitions[definition].group;
43+
output.push(<hr/>);
44+
}
45+
output.push(<div className='option' key={definition} value={definition}
46+
onClick={() => createEntity(definition)}>{definition}</div>);
47+
return output;
48+
})
49+
}
50+
</div>
51+
</div>
52+
);
53+
};

‎src/components/menu/EditMenu.js‎

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import React from 'react';
2+
import {removeSelectedEntity, cloneSelectedEntity} from '../../actions/entity';
3+
4+
var menuOptions = {
5+
'Clone': {group: 'base', callback: cloneSelectedEntity, needsEntity: true},
6+
'Remove': {group: 'base', callback: removeSelectedEntity, needsEntity: true}
7+
};
8+
9+
export const EditMenu = () => {
10+
var prevGroup = null;
11+
return (
12+
<div className='menu'>
13+
<div className='title'>Edit</div>
14+
<div className='options'>
15+
{
16+
Object.keys(menuOptions).map(key => {
17+
var option = menuOptions[key];
18+
var output = [];
19+
if (prevGroup === null) {
20+
prevGroup = option.group;
21+
} else if (prevGroup !== option.group) {
22+
prevGroup = option.group;
23+
output.push(<hr/>);
24+
}
25+
output.push(<div className='option' key={key} value={key}
26+
onClick={() => option.callback()}>{key}</div>);
27+
return output;
28+
})
29+
}
30+
</div>
31+
</div>
32+
);
33+
};

‎src/components/menu/ExportMenu.js‎

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import React from 'react';
2+
import Clipboard from 'clipboard';
3+
import {getSceneName, generateHtml} from '../../lib/exporter';
4+
5+
export class ExportMenu extends React.Component {
6+
componentDidMount () {
7+
var clipboard = new Clipboard('[data-action="copy-to-clipboard"]', {
8+
text: trigger => {
9+
return generateHtml();
10+
}
11+
});
12+
clipboard.on('error', e => {
13+
// @todo Show Error on the UI
14+
});
15+
}
16+
17+
saveToHTML () {
18+
var link = document.createElement('a');
19+
link.style.display = 'none';
20+
document.body.appendChild(link);
21+
function save (blob, filename) {
22+
link.href = URL.createObjectURL(blob);
23+
link.download = filename || 'ascene.html';
24+
link.click();
25+
// URL.revokeObjectURL(url); breaks Firefox...
26+
}
27+
function saveString (text, filename) {
28+
save(new Blob([ text ], { type: 'text/plain' }), filename);
29+
}
30+
var sceneName = getSceneName(document.querySelector('a-scene'));
31+
saveString(generateHtml(), sceneName);
32+
}
33+
34+
render () {
35+
return (
36+
<div className='menu'>
37+
<div className='title'>Export</div>
38+
<div className='options'>
39+
<div className='option' onClick={this.saveToHTML}>Save HTML</div>
40+
<div className='option' data-action='copy-to-clipboard'>Copy to clipboard</div>
41+
</div>
42+
</div>
43+
);
44+
}
45+
}

‎src/components/menu/Menu.js‎

Lines changed: 9 additions & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -1,141 +1,20 @@
11
import React from 'react';
2-
import Clipboard from 'clipboard';
3-
import Events from '../../lib/Events.js';
4-
import {getSceneName, generateHtml} from '../../lib/exporter';
5-
6-
var primitivesDefinitions = {
7-
'Entity': {group: 'entities', element: 'a-entity', components: {}},
8-
9-
'Box': {group: 'primitives', element: 'a-entity', components: {geometry: 'primitive:box', material: 'color:#f00'}},
10-
'Sphere': {group: 'primitives', element: 'a-entity', components: {geometry: 'primitive:sphere', material: 'color:#ff0'}},
11-
'Cylinder': {group: 'primitives', element: 'a-entity', components: {geometry: 'primitive:cylinder', material: 'color:#00f'}},
12-
'Plane': {group: 'primitives', element: 'a-entity', components: {geometry: 'primitive:plane', material: 'color:#fff'}},
13-
'Torus': {group: 'primitives', element: 'a-entity', components: {geometry: 'primitive:torus', material: 'color:#0f0'}},
14-
'TorusKnot': {group: 'primitives', element: 'a-entity', components: {geometry: 'primitive:torusKnot', material: 'color:#f0f'}},
15-
'Circle': {group: 'primitives', element: 'a-entity', components: {geometry: 'primitive:circle', material: 'color:#f0f'}},
16-
'Ring': {group: 'primitives', element: 'a-entity', components: {geometry: 'primitive:ring', material: 'color:#0ff'}},
17-
18-
'Ambient': {group: 'lights', element: 'a-entity', components: {light: 'type:ambient'}},
19-
'Directional': {group: 'lights', element: 'a-entity', components: {light: 'type:directional'}},
20-
'Hemisphere': {group: 'lights', element: 'a-entity', components: {light: 'type:hemisphere'}},
21-
'Point': {group: 'lights', element: 'a-entity', components: {light: 'type:point'}},
22-
'Spot': {group: 'lights', element: 'a-entity', components: {light: 'type:spot'}},
23-
24-
'Camera': {group: 'cameras', element: 'a-entity', components: {camera: ''}}
25-
};
26-
27-
function createEntity (definition) {
28-
Events.emit('createNewEntity', primitivesDefinitions[definition]);
29-
}
30-
31-
export const CreateMenu = props => {
32-
var prevGroup = null;
33-
var definitions = primitivesDefinitions;
34-
return (
35-
<div className="menu">
36-
<div className="title">Create</div>
37-
<div className="options">
38-
{
39-
Object.keys(definitions).map(function(definition) {
40-
var output = [];
41-
if (prevGroup === null) {
42-
prevGroup = definitions[definition].group;
43-
} else if (prevGroup !== definitions[definition].group) {
44-
prevGroup = definitions[definition].group;
45-
output.push(<hr/>);
46-
}
47-
output.push(<div className="option" key={definition} value={definition}
48-
onClick={() => createEntity(definition)}>{definition}</div>);
49-
return output;
50-
}.bind(this))
51-
}
52-
</div>
53-
</div>
54-
);
55-
};
56-
57-
export const EditMenu = () => {
58-
var prevGroup = null;
59-
var definitions = primitivesDefinitions;
60-
return (
61-
<div className="menu">
62-
<div className="title">Create</div>
63-
<div className="options">
64-
{
65-
Object.keys(definitions).map(function(definition) {
66-
var output = [];
67-
if (prevGroup === null) {
68-
prevGroup = definitions[definition].group;
69-
} else if (prevGroup !== definitions[definition].group) {
70-
prevGroup = definitions[definition].group;
71-
output.push(<hr/>);
72-
}
73-
output.push(<div className="option" key={definition} value={definition}>{definition}</div>);
74-
return output;
75-
}.bind(this))
76-
}
77-
</div>
78-
</div>
79-
);
80-
};
2+
import {CreateMenu} from './CreateMenu';
3+
import {EditMenu} from './EditMenu';
4+
import {ExportMenu} from './ExportMenu';
815

826
export class MenuWidget extends React.Component {
83-
componentDidMount() {
84-
var clipboard = new Clipboard('[data-action="copy-to-clipboard"]', {
85-
text: function (trigger) {
86-
return generateHtml();
87-
}
88-
});
89-
clipboard.on('error', function(e) {
90-
console.error('Error while copying to clipboard:', e.action, e.trigger);
91-
});
92-
}
93-
94-
update = e => {
95-
var value = e.target.value;
96-
this.setState({value: value});
97-
if (this.props.onChange)
98-
this.props.onChange(this.props.entity, this.props.componentname, this.props.name, value);
99-
}
100-
101-
saveToHTML() {
102-
var link = document.createElement('a');
103-
link.style.display = 'none';
104-
link.setAttribute('data-aframe-editor', 'download');
105-
document.body.appendChild(link);
106-
function save (blob, filename) {
107-
link.href = URL.createObjectURL(blob);
108-
link.download = filename || 'data.json';
109-
link.click();
110-
// URL.revokeObjectURL(url); breaks Firefox...
111-
}
112-
function saveString (text, filename) {
113-
save(new Blob([ text ], { type: 'text/plain' }), filename);
114-
}
115-
116-
var sceneName = getSceneName(document.querySelector('a-scene'));
117-
saveString(generateHtml(), sceneName);
118-
}
119-
120-
render() {
7+
render () {
1218
return (
122-
<div className="Panel" id="menubar">
123-
<div className="menu aframe-logo">
124-
<div className="title">
9+
<div className='panel' id='menubar'>
10+
<div className='menu aframe-logo'>
11+
<div className='title'>
12512
<span style={{color: '#ed3160'}}>A-</span>
12613
<span style={{color: '#fff'}}>Frame</span>
12714
</div>
12815
</div>
129-
<div className="menu">
130-
<div className="title">Export</div>
131-
<div className="options">
132-
<div className="option" onClick={this.saveToHTML}>Save HTML</div>
133-
<div className="option" data-action="copy-to-clipboard">Copy to clipboard</div>
134-
</div>
135-
</div>
136-
<div className="menu">
137-
<div className="title">Edit</div>
138-
</div>
16+
<ExportMenu/>
17+
<EditMenu/>
13918
<CreateMenu/>
14019
</div>
14120
);

0 commit comments

Comments
 (0)