senangwebs-aframe-editor 1.6.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.babelrc +3 -0
- package/.editorconfig +12 -0
- package/.eslintignore +2 -0
- package/.eslintrc +40 -0
- package/.github/workflows/ci.yml +39 -0
- package/.husky/pre-commit +4 -0
- package/.prettierignore +1 -0
- package/.prettierrc.json +5 -0
- package/.stylelintrc +12 -0
- package/LICENSE +21 -0
- package/README.md +75 -0
- package/assets/gltf.svg +49 -0
- package/dist/aframe-inspector.js +106250 -0
- package/dist/aframe-inspector.js.map +1 -0
- package/dist/aframe-inspector.min.js +29040 -0
- package/dist/aframe-inspector.min.js.LICENSE.txt +56 -0
- package/dist/aframe-inspector.min.js.map +1 -0
- package/examples/360video.html +48 -0
- package/examples/colors.html +18 -0
- package/examples/controllers.html +60 -0
- package/examples/embedded-zoom.html +78 -0
- package/examples/embedded.html +79 -0
- package/examples/empty.html +13 -0
- package/examples/index-aframe.html +66 -0
- package/examples/index.html +71 -0
- package/examples/supercraft.html +6 -0
- package/index.html +8 -0
- package/package.json +84 -0
- package/senangwebs-webverse-editor.png +0 -0
- package/src/components/AwesomeIcon.js +53 -0
- package/src/components/Collapsible.js +57 -0
- package/src/components/EntityRepresentation.js +83 -0
- package/src/components/Main.js +222 -0
- package/src/components/__tests__/Collapsible.test.js +30 -0
- package/src/components/components/AddComponent.js +104 -0
- package/src/components/components/CommonComponents.js +160 -0
- package/src/components/components/Component.js +151 -0
- package/src/components/components/ComponentsContainer.js +52 -0
- package/src/components/components/DefaultComponents.js +1 -0
- package/src/components/components/Mixins.js +83 -0
- package/src/components/components/PropertyRow.js +145 -0
- package/src/components/components/Sidebar.js +51 -0
- package/src/components/icons/BackViewIcon.js +27 -0
- package/src/components/icons/BottomViewIcon.js +26 -0
- package/src/components/icons/FrontViewIcon.js +23 -0
- package/src/components/icons/LeftViewIcon.js +24 -0
- package/src/components/icons/PerspectiveIcon.js +23 -0
- package/src/components/icons/PrimitiveBoxIcon.js +143 -0
- package/src/components/icons/PrimitiveConeIcon.js +44 -0
- package/src/components/icons/PrimitiveCylinderIcon.js +51 -0
- package/src/components/icons/PrimitiveEmptyEntityIcon.js +78 -0
- package/src/components/icons/PrimitiveImageIcon.js +86 -0
- package/src/components/icons/PrimitiveLightIcon.js +107 -0
- package/src/components/icons/PrimitivePlaneIcon.js +87 -0
- package/src/components/icons/PrimitiveSphereIcon.js +39 -0
- package/src/components/icons/PrimitiveTextIcon.js +89 -0
- package/src/components/icons/PrimitiveTorusIcon.js +31 -0
- package/src/components/icons/RightViewIcon.js +24 -0
- package/src/components/icons/TopViewIcon.js +24 -0
- package/src/components/modals/Modal.js +107 -0
- package/src/components/modals/ModalHelp.js +97 -0
- package/src/components/modals/ModalPrimitive.js +114 -0
- package/src/components/modals/ModalTextures.js +430 -0
- package/src/components/scenegraph/Entity.js +142 -0
- package/src/components/scenegraph/SceneGraph.js +337 -0
- package/src/components/scenegraph/Toolbar.js +147 -0
- package/src/components/viewport/CameraToolbar.js +122 -0
- package/src/components/viewport/TransformToolbar.js +102 -0
- package/src/components/viewport/ViewportHUD.js +33 -0
- package/src/components/widgets/BooleanWidget.js +49 -0
- package/src/components/widgets/ColorWidget.js +89 -0
- package/src/components/widgets/InputWidget.js +42 -0
- package/src/components/widgets/NumberWidget.js +179 -0
- package/src/components/widgets/SelectWidget.js +58 -0
- package/src/components/widgets/TextureWidget.js +252 -0
- package/src/components/widgets/Vec2Widget.js +55 -0
- package/src/components/widgets/Vec3Widget.js +58 -0
- package/src/components/widgets/Vec4Widget.js +61 -0
- package/src/components/widgets/index.js +9 -0
- package/src/index.js +301 -0
- package/src/lib/EditorControls.js +336 -0
- package/src/lib/Events.js +6 -0
- package/src/lib/TransformControls.js +1365 -0
- package/src/lib/assetsLoader.js +43 -0
- package/src/lib/assetsUtils.js +30 -0
- package/src/lib/cameras.js +121 -0
- package/src/lib/entity.js +556 -0
- package/src/lib/history.js +30 -0
- package/src/lib/raycaster.js +129 -0
- package/src/lib/shortcuts.js +211 -0
- package/src/lib/utils.js +118 -0
- package/src/lib/viewport.js +268 -0
- package/src/style/components.styl +275 -0
- package/src/style/entity.styl +22 -0
- package/src/style/help.styl +40 -0
- package/src/style/index.styl +358 -0
- package/src/style/lib.styl +41 -0
- package/src/style/primitiveModal.styl +90 -0
- package/src/style/scenegraph.styl +173 -0
- package/src/style/select.styl +71 -0
- package/src/style/textureModal.styl +220 -0
- package/src/style/viewport.styl +168 -0
- package/src/style/widgets.styl +71 -0
- package/webpack.config.js +65 -0
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
/* eslint-disable no-unused-vars, react/no-danger */
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import PropTypes from 'prop-types';
|
|
4
|
+
import { faSearch, faTimes } from '@fortawesome/free-solid-svg-icons';
|
|
5
|
+
import { AwesomeIcon } from '../AwesomeIcon';
|
|
6
|
+
import debounce from 'lodash.debounce';
|
|
7
|
+
|
|
8
|
+
import Entity from './Entity';
|
|
9
|
+
import Toolbar from './Toolbar';
|
|
10
|
+
import Events from '../../lib/Events';
|
|
11
|
+
import CameraToolbar from '../viewport/CameraToolbar';
|
|
12
|
+
|
|
13
|
+
export default class SceneGraph extends React.Component {
|
|
14
|
+
static propTypes = {
|
|
15
|
+
id: PropTypes.string,
|
|
16
|
+
onChange: PropTypes.func,
|
|
17
|
+
scene: PropTypes.object,
|
|
18
|
+
selectedEntity: PropTypes.object,
|
|
19
|
+
visible: PropTypes.bool
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
static defaultProps = {
|
|
23
|
+
selectedEntity: '',
|
|
24
|
+
index: -1,
|
|
25
|
+
id: 'left-sidebar'
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
constructor(props) {
|
|
29
|
+
super(props);
|
|
30
|
+
this.state = {
|
|
31
|
+
entities: [],
|
|
32
|
+
expandedElements: new WeakMap([[props.scene, true]]),
|
|
33
|
+
filter: '',
|
|
34
|
+
filteredEntities: [],
|
|
35
|
+
selectedIndex: -1
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
this.rebuildEntityOptions = debounce(
|
|
39
|
+
this.rebuildEntityOptions.bind(this),
|
|
40
|
+
1000
|
|
41
|
+
);
|
|
42
|
+
this.updateFilteredEntities = debounce(
|
|
43
|
+
this.updateFilteredEntities.bind(this),
|
|
44
|
+
500
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
componentDidMount() {
|
|
49
|
+
this.rebuildEntityOptions();
|
|
50
|
+
Events.on('entityidchange', this.rebuildEntityOptions);
|
|
51
|
+
Events.on('entitycreated', this.rebuildEntityOptions);
|
|
52
|
+
Events.on('entityclone', this.rebuildEntityOptions);
|
|
53
|
+
Events.on('entityupdate', (detail) => {
|
|
54
|
+
if (detail.component === 'mixin') {
|
|
55
|
+
this.rebuildEntityOptions();
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Selected entity updated from somewhere else in the app.
|
|
62
|
+
*/
|
|
63
|
+
componentDidUpdate(prevProps) {
|
|
64
|
+
if (prevProps.selectedEntity !== this.props.selectedEntity) {
|
|
65
|
+
this.selectEntity(this.props.selectedEntity);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
selectEntity = (entity) => {
|
|
70
|
+
let found = false;
|
|
71
|
+
for (let i = 0; i < this.state.filteredEntities.length; i++) {
|
|
72
|
+
const entityOption = this.state.filteredEntities[i];
|
|
73
|
+
if (entityOption.entity === entity) {
|
|
74
|
+
this.setState({ selectedEntity: entity, selectedIndex: i });
|
|
75
|
+
// Make sure selected value is visible in scenegraph
|
|
76
|
+
this.expandToRoot(entity);
|
|
77
|
+
if (this.props.onChange) {
|
|
78
|
+
this.props.onChange(entity);
|
|
79
|
+
}
|
|
80
|
+
Events.emit('entityselect', entity, true);
|
|
81
|
+
found = true;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (!found) {
|
|
86
|
+
this.setState({ selectedEntity: null, selectedIndex: -1 });
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
rebuildEntityOptions = () => {
|
|
91
|
+
const entities = [{ depth: 0, entity: this.props.scene }];
|
|
92
|
+
|
|
93
|
+
function treeIterate(element, depth) {
|
|
94
|
+
if (!element) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
depth += 1;
|
|
98
|
+
|
|
99
|
+
for (let i = 0; i < element.children.length; i++) {
|
|
100
|
+
let entity = element.children[i];
|
|
101
|
+
|
|
102
|
+
if (
|
|
103
|
+
entity.dataset.isInspector ||
|
|
104
|
+
!entity.isEntity ||
|
|
105
|
+
entity.isInspector ||
|
|
106
|
+
'aframeInspector' in entity.dataset
|
|
107
|
+
) {
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
entities.push({ entity: entity, depth: depth });
|
|
112
|
+
|
|
113
|
+
treeIterate(entity, depth);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
treeIterate(this.props.scene, 0);
|
|
117
|
+
|
|
118
|
+
this.setState({
|
|
119
|
+
entities: entities,
|
|
120
|
+
filteredEntities: this.getFilteredEntities(this.state.filter, entities)
|
|
121
|
+
});
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
selectIndex = (index) => {
|
|
125
|
+
if (index >= 0 && index < this.state.entities.length) {
|
|
126
|
+
this.selectEntity(this.state.entities[index].entity);
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
onFilterKeyUp = (event) => {
|
|
131
|
+
if (event.key === 'Escape') { // Use event.key for consistency
|
|
132
|
+
this.clearFilter();
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
onKeyDown = (event) => {
|
|
137
|
+
switch (event.key) {
|
|
138
|
+
case 'ArrowLeft':
|
|
139
|
+
case 'ArrowUp':
|
|
140
|
+
case 'ArrowRight':
|
|
141
|
+
case 'ArrowDown':
|
|
142
|
+
event.preventDefault();
|
|
143
|
+
event.stopPropagation();
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
onKeyUp = (event) => {
|
|
149
|
+
if (this.props.selectedEntity === null) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
switch (event.key) {
|
|
154
|
+
case 'ArrowLeft':
|
|
155
|
+
if (this.isExpanded(this.props.selectedEntity)) {
|
|
156
|
+
this.toggleExpandedCollapsed(this.props.selectedEntity);
|
|
157
|
+
}
|
|
158
|
+
break;
|
|
159
|
+
case 'ArrowUp':
|
|
160
|
+
this.selectIndex(
|
|
161
|
+
this.previousExpandedIndexTo(this.state.selectedIndex)
|
|
162
|
+
);
|
|
163
|
+
break;
|
|
164
|
+
case 'ArrowRight':
|
|
165
|
+
if (!this.isExpanded(this.props.selectedEntity)) {
|
|
166
|
+
this.toggleExpandedCollapsed(this.props.selectedEntity);
|
|
167
|
+
}
|
|
168
|
+
break;
|
|
169
|
+
case 'ArrowDown':
|
|
170
|
+
this.selectIndex(this.nextExpandedIndexTo(this.state.selectedIndex));
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
getFilteredEntities(filter, entities) {
|
|
176
|
+
entities = entities || this.state.entities;
|
|
177
|
+
if (!filter) {
|
|
178
|
+
return entities;
|
|
179
|
+
}
|
|
180
|
+
return entities.filter((entityOption) => {
|
|
181
|
+
return filterEntity(entityOption.entity, filter || this.state.filter);
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
isVisibleInSceneGraph = (x) => {
|
|
186
|
+
let curr = x.parentNode;
|
|
187
|
+
if (!curr) {
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
while (curr !== undefined && curr.isEntity) {
|
|
191
|
+
if (!this.isExpanded(curr)) {
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
curr = curr.parentNode;
|
|
195
|
+
}
|
|
196
|
+
return true;
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
isExpanded = (x) => this.state.expandedElements.get(x) === true;
|
|
200
|
+
|
|
201
|
+
toggleExpandedCollapsed = (x) => {
|
|
202
|
+
this.setState({
|
|
203
|
+
expandedElements: this.state.expandedElements.set(x, !this.isExpanded(x))
|
|
204
|
+
});
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
expandToRoot = (x) => {
|
|
208
|
+
// Expand element all the way to the scene element
|
|
209
|
+
let curr = x.parentNode;
|
|
210
|
+
while (curr !== undefined && curr.isEntity) {
|
|
211
|
+
this.state.expandedElements.set(curr, true);
|
|
212
|
+
curr = curr.parentNode;
|
|
213
|
+
}
|
|
214
|
+
this.setState({ expandedElements: this.state.expandedElements });
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
previousExpandedIndexTo = (i) => {
|
|
218
|
+
for (let prevIter = i - 1; prevIter >= 0; prevIter--) {
|
|
219
|
+
const prevEl = this.state.entities[prevIter].entity;
|
|
220
|
+
if (this.isVisibleInSceneGraph(prevEl)) {
|
|
221
|
+
return prevIter;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return -1;
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
nextExpandedIndexTo = (i) => {
|
|
228
|
+
for (
|
|
229
|
+
let nextIter = i + 1;
|
|
230
|
+
nextIter < this.state.entities.length;
|
|
231
|
+
nextIter++
|
|
232
|
+
) {
|
|
233
|
+
const nextEl = this.state.entities[nextIter].entity;
|
|
234
|
+
if (this.isVisibleInSceneGraph(nextEl)) {
|
|
235
|
+
return nextIter;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return -1;
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
onChangeFilter = (evt) => {
|
|
242
|
+
const filter = evt.target.value;
|
|
243
|
+
this.setState({ filter: filter });
|
|
244
|
+
this.updateFilteredEntities(filter);
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
updateFilteredEntities(filter) {
|
|
248
|
+
this.setState({
|
|
249
|
+
filteredEntities: this.getFilteredEntities(filter)
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
clearFilter = () => {
|
|
254
|
+
this.setState({ filter: '' });
|
|
255
|
+
this.updateFilteredEntities('');
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
renderEntities = () => {
|
|
259
|
+
return this.state.filteredEntities.map((entityOption, idx) => {
|
|
260
|
+
if (
|
|
261
|
+
!this.isVisibleInSceneGraph(entityOption.entity) &&
|
|
262
|
+
!this.state.filter
|
|
263
|
+
) {
|
|
264
|
+
return null;
|
|
265
|
+
}
|
|
266
|
+
return (
|
|
267
|
+
<Entity
|
|
268
|
+
{...entityOption}
|
|
269
|
+
key={idx}
|
|
270
|
+
isFiltering={!!this.state.filter}
|
|
271
|
+
isExpanded={this.isExpanded(entityOption.entity)}
|
|
272
|
+
isSelected={this.props.selectedEntity === entityOption.entity}
|
|
273
|
+
selectEntity={this.selectEntity}
|
|
274
|
+
toggleExpandedCollapsed={this.toggleExpandedCollapsed}
|
|
275
|
+
/>
|
|
276
|
+
);
|
|
277
|
+
});
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
render() {
|
|
281
|
+
// To hide the SceneGraph we have to hide its parent too (#left-sidebar).
|
|
282
|
+
if (!this.props.visible) {
|
|
283
|
+
return null;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const clearFilter = this.state.filter ? (
|
|
287
|
+
<a onClick={this.clearFilter} className="button">
|
|
288
|
+
<AwesomeIcon icon={faTimes} />
|
|
289
|
+
</a>
|
|
290
|
+
) : null;
|
|
291
|
+
|
|
292
|
+
return (
|
|
293
|
+
<div id="scenegraph" className="scenegraph">
|
|
294
|
+
<div className="scenegraph-toolbar">
|
|
295
|
+
<Toolbar />
|
|
296
|
+
<div className="search">
|
|
297
|
+
<input
|
|
298
|
+
id="filter"
|
|
299
|
+
placeholder="Search..."
|
|
300
|
+
onChange={this.onChangeFilter}
|
|
301
|
+
onKeyUp={this.onFilterKeyUp}
|
|
302
|
+
value={this.state.filter}
|
|
303
|
+
/>
|
|
304
|
+
{clearFilter}
|
|
305
|
+
{!this.state.filter && <AwesomeIcon icon={faSearch} />}
|
|
306
|
+
</div>
|
|
307
|
+
</div>
|
|
308
|
+
<div
|
|
309
|
+
className="outliner"
|
|
310
|
+
tabIndex="0"
|
|
311
|
+
onKeyDown={this.onKeyDown}
|
|
312
|
+
onKeyUp={this.onKeyUp}
|
|
313
|
+
>
|
|
314
|
+
{this.renderEntities()}
|
|
315
|
+
</div>
|
|
316
|
+
</div>
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function filterEntity(entity, filter) {
|
|
322
|
+
if (!filter) {
|
|
323
|
+
return true;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Check if the ID, tagName, class, selector includes the filter.
|
|
327
|
+
if (
|
|
328
|
+
entity.id.toUpperCase().indexOf(filter.toUpperCase()) !== -1 ||
|
|
329
|
+
entity.tagName.toUpperCase().indexOf(filter.toUpperCase()) !== -1 ||
|
|
330
|
+
entity.classList.contains(filter) ||
|
|
331
|
+
entity.matches(filter)
|
|
332
|
+
) {
|
|
333
|
+
return true;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
return false;
|
|
337
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {
|
|
3
|
+
faPlus,
|
|
4
|
+
faPause,
|
|
5
|
+
faPlay,
|
|
6
|
+
// faCube, // Removed as it's not used directly here anymore
|
|
7
|
+
// faCode, // Removed as it's not used directly here anymore
|
|
8
|
+
// faFloppyDisk // Removed as it's not used directly here anymore
|
|
9
|
+
} from '@fortawesome/free-solid-svg-icons';
|
|
10
|
+
import { AwesomeIcon } from '../AwesomeIcon';
|
|
11
|
+
import Events from '../../lib/Events';
|
|
12
|
+
import ModalPrimitive from '../modals/ModalPrimitive'; // Import the new modal component
|
|
13
|
+
|
|
14
|
+
function filterHelpers(scene, visible) {
|
|
15
|
+
scene.traverse((o) => {
|
|
16
|
+
if (o.userData.source === 'INSPECTOR') {
|
|
17
|
+
o.visible = visible;
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function getSceneName(scene) {
|
|
23
|
+
return scene.id || slugify(window.location.host + window.location.pathname);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Slugify the string removing non-word chars and spaces
|
|
28
|
+
* @param {string} text String to slugify
|
|
29
|
+
* @return {string} Slugified string
|
|
30
|
+
*/
|
|
31
|
+
function slugify(text) {
|
|
32
|
+
return text
|
|
33
|
+
.toString()
|
|
34
|
+
.toLowerCase()
|
|
35
|
+
.replace(/\s+/g, '-') // Replace spaces with -
|
|
36
|
+
.replace(/[^\w-]+/g, '-') // Replace all non-word chars with -
|
|
37
|
+
.replace(/--+/g, '-') // Replace multiple - with single -
|
|
38
|
+
.replace(/^-+/, '') // Trim - from start of text
|
|
39
|
+
.replace(/-+$/, ''); // Trim - from end of text
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Tools and actions.
|
|
44
|
+
*/
|
|
45
|
+
export default class Toolbar extends React.Component {
|
|
46
|
+
constructor(props) {
|
|
47
|
+
super(props);
|
|
48
|
+
|
|
49
|
+
this.state = {
|
|
50
|
+
isPlaying: false,
|
|
51
|
+
isAddEntityModalOpen: false // Add state for modal visibility
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
exportSceneToGLTF() {
|
|
56
|
+
const sceneName = getSceneName(AFRAME.scenes[0]);
|
|
57
|
+
const scene = AFRAME.scenes[0].object3D;
|
|
58
|
+
filterHelpers(scene, false);
|
|
59
|
+
AFRAME.INSPECTOR.exporters.gltf.parse(
|
|
60
|
+
scene,
|
|
61
|
+
function (buffer) {
|
|
62
|
+
filterHelpers(scene, true);
|
|
63
|
+
const blob = new Blob([buffer], { type: 'application/octet-stream' });
|
|
64
|
+
saveBlob(blob, sceneName + '.glb');
|
|
65
|
+
},
|
|
66
|
+
function (error) {
|
|
67
|
+
console.error(error);
|
|
68
|
+
},
|
|
69
|
+
{ binary: true }
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Renamed original addEntity to toggleAddEntityModal
|
|
74
|
+
toggleAddEntityModal = () => {
|
|
75
|
+
this.setState({ isAddEntityModalOpen: !this.state.isAddEntityModalOpen });
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
// New function to handle entity creation based on modal selection
|
|
79
|
+
createEntity = (primitiveType) => {
|
|
80
|
+
if (primitiveType) {
|
|
81
|
+
Events.emit('entitycreate', { element: primitiveType, components: {} });
|
|
82
|
+
}
|
|
83
|
+
this.setState({ isAddEntityModalOpen: false }); // Close modal after selection or cancellation
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
toggleScenePlaying = () => {
|
|
88
|
+
if (this.state.isPlaying) {
|
|
89
|
+
AFRAME.scenes[0].pause();
|
|
90
|
+
this.setState({ isPlaying: false });
|
|
91
|
+
AFRAME.scenes[0].isPlaying = true;
|
|
92
|
+
document.getElementById('aframeInspectorMouseCursor').play();
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
AFRAME.scenes[0].isPlaying = false;
|
|
96
|
+
AFRAME.scenes[0].play();
|
|
97
|
+
this.setState({ isPlaying: true });
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
render() {
|
|
101
|
+
// const watcherTitle = 'Write changes with aframe-watcher.'; // Keep or remove as needed
|
|
102
|
+
|
|
103
|
+
return (
|
|
104
|
+
<div id="toolbar">
|
|
105
|
+
<div className="toolbarActions">
|
|
106
|
+
<a
|
|
107
|
+
className="button"
|
|
108
|
+
title="Add a new entity"
|
|
109
|
+
onClick={this.toggleAddEntityModal} // Changed onClick handler
|
|
110
|
+
>
|
|
111
|
+
<AwesomeIcon icon={faPlus} />
|
|
112
|
+
<span>Add Entity</span>
|
|
113
|
+
</a>
|
|
114
|
+
{/* Play/Pause Button */}
|
|
115
|
+
<a
|
|
116
|
+
id="playPauseScene"
|
|
117
|
+
className="button"
|
|
118
|
+
title={this.state.isPlaying ? 'Pause scene' : 'Resume scene'}
|
|
119
|
+
onClick={this.toggleScenePlaying}
|
|
120
|
+
>
|
|
121
|
+
{this.state.isPlaying ? (
|
|
122
|
+
<>
|
|
123
|
+
<AwesomeIcon icon={faPause} />
|
|
124
|
+
<span>Pause Scene</span>
|
|
125
|
+
</>
|
|
126
|
+
) : (
|
|
127
|
+
<>
|
|
128
|
+
<AwesomeIcon icon={faPlay} />
|
|
129
|
+
<span>Play Scene</span>
|
|
130
|
+
</>
|
|
131
|
+
)}
|
|
132
|
+
</a>
|
|
133
|
+
{/* Add other buttons like export, save if needed */}
|
|
134
|
+
</div>
|
|
135
|
+
|
|
136
|
+
{/* Conditionally render the modal */}
|
|
137
|
+
{this.state.isAddEntityModalOpen && (
|
|
138
|
+
<ModalPrimitive
|
|
139
|
+
isOpen={this.state.isAddEntityModalOpen}
|
|
140
|
+
onClose={this.toggleAddEntityModal} // Pass function to close modal
|
|
141
|
+
onSelectPrimitive={this.createEntity} // Pass function to create entity
|
|
142
|
+
/>
|
|
143
|
+
)}
|
|
144
|
+
</div>
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import Select from 'react-select';
|
|
3
|
+
import Events from '../../lib/Events';
|
|
4
|
+
|
|
5
|
+
// Import custom SVG icons
|
|
6
|
+
import PerspectiveIcon from '../icons/PerspectiveIcon';
|
|
7
|
+
import LeftViewIcon from '../icons/LeftViewIcon';
|
|
8
|
+
import RightViewIcon from '../icons/RightViewIcon';
|
|
9
|
+
import TopViewIcon from '../icons/TopViewIcon';
|
|
10
|
+
import BottomViewIcon from '../icons/BottomViewIcon';
|
|
11
|
+
import BackViewIcon from '../icons/BackViewIcon';
|
|
12
|
+
import FrontViewIcon from '../icons/FrontViewIcon';
|
|
13
|
+
|
|
14
|
+
const options = [
|
|
15
|
+
{
|
|
16
|
+
value: 'perspective',
|
|
17
|
+
event: 'cameraperspectivetoggle',
|
|
18
|
+
payload: null,
|
|
19
|
+
label: 'Perspective',
|
|
20
|
+
icon: <PerspectiveIcon width="24" height="24" /> // Use custom icon
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
value: 'ortholeft',
|
|
24
|
+
event: 'cameraorthographictoggle',
|
|
25
|
+
payload: 'left',
|
|
26
|
+
label: 'Left View',
|
|
27
|
+
icon: <LeftViewIcon width="24" height="24" /> // Use custom icon
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
value: 'orthoright',
|
|
31
|
+
event: 'cameraorthographictoggle',
|
|
32
|
+
payload: 'right',
|
|
33
|
+
label: 'Right View',
|
|
34
|
+
icon: <RightViewIcon width="24" height="24" /> // Use custom icon
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
value: 'orthotop',
|
|
38
|
+
event: 'cameraorthographictoggle',
|
|
39
|
+
payload: 'top',
|
|
40
|
+
label: 'Top View',
|
|
41
|
+
icon: <TopViewIcon width="24" height="24" /> // Use custom icon
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
value: 'orthobottom',
|
|
45
|
+
event: 'cameraorthographictoggle',
|
|
46
|
+
payload: 'bottom',
|
|
47
|
+
label: 'Bottom View',
|
|
48
|
+
icon: <BottomViewIcon width="24" height="24" /> // Use custom icon
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
value: 'orthoback',
|
|
52
|
+
event: 'cameraorthographictoggle',
|
|
53
|
+
payload: 'back',
|
|
54
|
+
label: 'Back View',
|
|
55
|
+
icon: <BackViewIcon width="24" height="24" /> // Use custom icon
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
value: 'orthofront',
|
|
59
|
+
event: 'cameraorthographictoggle',
|
|
60
|
+
payload: 'front',
|
|
61
|
+
label: 'Front View',
|
|
62
|
+
icon: <FrontViewIcon width="24" height="24" /> // Use custom icon
|
|
63
|
+
}
|
|
64
|
+
];
|
|
65
|
+
|
|
66
|
+
export default class CameraToolbar extends React.Component {
|
|
67
|
+
constructor(props) {
|
|
68
|
+
super(props);
|
|
69
|
+
this.state = {
|
|
70
|
+
selectedCamera: 'perspective'
|
|
71
|
+
};
|
|
72
|
+
this.justChangedCamera = false;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
componentDidMount() {
|
|
76
|
+
Events.on('cameratoggle', (data) => {
|
|
77
|
+
if (this.justChangedCamera) {
|
|
78
|
+
// Prevent recursion.
|
|
79
|
+
this.justChangedCamera = false;
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
// Check if the incoming data value exists in our options
|
|
83
|
+
const isValidOption = options.some((opt) => opt.value === data.value);
|
|
84
|
+
if (isValidOption) {
|
|
85
|
+
this.setState({ selectedCamera: data.value });
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
onChange(option) {
|
|
91
|
+
console.log('Selected Camera Option:', option);
|
|
92
|
+
this.justChangedCamera = true;
|
|
93
|
+
this.setState({ selectedCamera: option.value });
|
|
94
|
+
Events.emit(option.event, option.payload);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
render() {
|
|
98
|
+
// Custom formatOption to render label and icon
|
|
99
|
+
const formatOptionLabel = ({ label, icon }) => (
|
|
100
|
+
<div style={{ display: 'flex', alignItems: 'center' }}>
|
|
101
|
+
{icon}
|
|
102
|
+
<span style={{ marginLeft: '8px' }}>{label}</span>
|
|
103
|
+
</div>
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
return (
|
|
107
|
+
<div id="cameraToolbar">
|
|
108
|
+
<Select
|
|
109
|
+
id="cameraSelect"
|
|
110
|
+
classNamePrefix="select"
|
|
111
|
+
options={options}
|
|
112
|
+
value={options.find(
|
|
113
|
+
(option) => option.value === this.state.selectedCamera
|
|
114
|
+
)}
|
|
115
|
+
isSearchable={false}
|
|
116
|
+
onChange={this.onChange.bind(this)}
|
|
117
|
+
formatOptionLabel={formatOptionLabel}
|
|
118
|
+
/>
|
|
119
|
+
</div>
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {
|
|
3
|
+
faArrowsAlt,
|
|
4
|
+
faRotateRight,
|
|
5
|
+
faUpRightAndDownLeftFromCenter
|
|
6
|
+
} from '@fortawesome/free-solid-svg-icons';
|
|
7
|
+
import { AwesomeIcon } from '../AwesomeIcon';
|
|
8
|
+
import clsx from 'clsx';
|
|
9
|
+
import Events from '../../lib/Events';
|
|
10
|
+
|
|
11
|
+
var TransformButtons = [
|
|
12
|
+
{ value: 'translate', icon: <AwesomeIcon icon={faArrowsAlt} /> },
|
|
13
|
+
{ value: 'rotate', icon: <AwesomeIcon icon={faRotateRight} /> },
|
|
14
|
+
{
|
|
15
|
+
value: 'scale',
|
|
16
|
+
icon: <AwesomeIcon icon={faUpRightAndDownLeftFromCenter} />
|
|
17
|
+
}
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
export default class TransformToolbar extends React.Component {
|
|
21
|
+
constructor(props) {
|
|
22
|
+
super(props);
|
|
23
|
+
this.state = {
|
|
24
|
+
selectedTransform: 'translate',
|
|
25
|
+
localSpace: false
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
componentDidMount() {
|
|
30
|
+
Events.on('transformmodechange', (mode) => {
|
|
31
|
+
this.setState({ selectedTransform: mode });
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
Events.on('transformspacechange', () => {
|
|
35
|
+
Events.emit(
|
|
36
|
+
'transformspacechanged',
|
|
37
|
+
this.state.localSpace ? 'world' : 'local'
|
|
38
|
+
);
|
|
39
|
+
this.setState({ localSpace: !this.state.localSpace });
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
changeTransformMode = (mode) => {
|
|
44
|
+
this.setState({ selectedTransform: mode });
|
|
45
|
+
Events.emit('transformmodechange', mode);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
onLocalChange = (e) => {
|
|
49
|
+
const local = e.target.checked;
|
|
50
|
+
this.setState({ localSpace: local });
|
|
51
|
+
Events.emit('transformspacechanged', local ? 'local' : 'world');
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
renderTransformButtons = () => {
|
|
55
|
+
return TransformButtons.map(
|
|
56
|
+
function (option, i) {
|
|
57
|
+
var selected = option.value === this.state.selectedTransform;
|
|
58
|
+
var classes = clsx({
|
|
59
|
+
button: true,
|
|
60
|
+
active: selected
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<a
|
|
65
|
+
title={option.value}
|
|
66
|
+
key={i}
|
|
67
|
+
onClick={this.changeTransformMode.bind(this, option.value)}
|
|
68
|
+
className={classes}
|
|
69
|
+
>
|
|
70
|
+
{option.icon}
|
|
71
|
+
</a>
|
|
72
|
+
);
|
|
73
|
+
}.bind(this)
|
|
74
|
+
);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
render() {
|
|
78
|
+
return (
|
|
79
|
+
<div id="transformToolbar" className="toolbarButtons">
|
|
80
|
+
{this.renderTransformButtons()}
|
|
81
|
+
{/* <span className="local-transform">
|
|
82
|
+
<input
|
|
83
|
+
id="local"
|
|
84
|
+
type="checkbox"
|
|
85
|
+
title="Toggle between local and world space transforms"
|
|
86
|
+
checked={
|
|
87
|
+
this.state.localSpace || this.state.selectedTransform === 'scale'
|
|
88
|
+
}
|
|
89
|
+
disabled={this.state.selectedTransform === 'scale'}
|
|
90
|
+
onChange={this.onLocalChange}
|
|
91
|
+
/>
|
|
92
|
+
<label
|
|
93
|
+
htmlFor="local"
|
|
94
|
+
title="Toggle between local and world space transforms"
|
|
95
|
+
>
|
|
96
|
+
Local Space Transform
|
|
97
|
+
</label>
|
|
98
|
+
</span> */}
|
|
99
|
+
</div>
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
}
|