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,556 @@
|
|
|
1
|
+
import Events from './Events';
|
|
2
|
+
import { equal } from './utils';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Update a component.
|
|
6
|
+
*
|
|
7
|
+
* @param {Element} entity - Entity to modify.
|
|
8
|
+
* @param {string} component - component name
|
|
9
|
+
* @param {string} property - property name, use empty string if component is single property or if value is an object
|
|
10
|
+
* @param {string|number|object} value - New value.
|
|
11
|
+
*/
|
|
12
|
+
export function updateEntity(entity, component, property, value) {
|
|
13
|
+
if (property) {
|
|
14
|
+
if (value === null || value === undefined) {
|
|
15
|
+
// Remove property.
|
|
16
|
+
entity.removeAttribute(component, property);
|
|
17
|
+
} else {
|
|
18
|
+
// Set property.
|
|
19
|
+
entity.setAttribute(component, property, value);
|
|
20
|
+
}
|
|
21
|
+
} else {
|
|
22
|
+
if (value === null || value === undefined) {
|
|
23
|
+
// Remove component.
|
|
24
|
+
entity.removeAttribute(component);
|
|
25
|
+
} else {
|
|
26
|
+
// Set component.
|
|
27
|
+
entity.setAttribute(component, value);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
Events.emit('entityupdate', { entity, component, property, value });
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Remove an entity.
|
|
36
|
+
*
|
|
37
|
+
* @param {Element} entity Entity to remove.
|
|
38
|
+
* @param {boolean} force (Optional) If true it won't ask for confirmation.
|
|
39
|
+
*/
|
|
40
|
+
export function removeEntity(entity, force) {
|
|
41
|
+
if (entity) {
|
|
42
|
+
if (
|
|
43
|
+
force === true ||
|
|
44
|
+
confirm(
|
|
45
|
+
'Do you really want to remove entity `' +
|
|
46
|
+
(entity.id || entity.tagName) +
|
|
47
|
+
'`?'
|
|
48
|
+
)
|
|
49
|
+
) {
|
|
50
|
+
var closest = findClosestEntity(entity);
|
|
51
|
+
entity.parentNode.removeChild(entity);
|
|
52
|
+
AFRAME.INSPECTOR.selectEntity(closest);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function findClosestEntity(entity) {
|
|
58
|
+
// First we try to find the after the entity
|
|
59
|
+
var nextEntity = entity.nextElementSibling;
|
|
60
|
+
while (nextEntity && (!nextEntity.isEntity || nextEntity.isInspector)) {
|
|
61
|
+
nextEntity = nextEntity.nextElementSibling;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Return if we found it
|
|
65
|
+
if (nextEntity && nextEntity.isEntity && !nextEntity.isInspector) {
|
|
66
|
+
return nextEntity;
|
|
67
|
+
}
|
|
68
|
+
// Otherwise try to find before the entity
|
|
69
|
+
var prevEntity = entity.previousElementSibling;
|
|
70
|
+
while (prevEntity && (!prevEntity.isEntity || prevEntity.isInspector)) {
|
|
71
|
+
prevEntity = prevEntity.previousElementSibling;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Return if we found it
|
|
75
|
+
if (prevEntity && prevEntity.isEntity && !prevEntity.isInspector) {
|
|
76
|
+
return prevEntity;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Remove the selected entity
|
|
84
|
+
* @param {boolean} force (Optional) If true it won't ask for confirmation
|
|
85
|
+
*/
|
|
86
|
+
export function removeSelectedEntity(force) {
|
|
87
|
+
if (AFRAME.INSPECTOR.selectedEntity) {
|
|
88
|
+
removeEntity(AFRAME.INSPECTOR.selectedEntity, force);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Insert an node after a referenced node.
|
|
94
|
+
* @param {Element} newNode Node to insert.
|
|
95
|
+
* @param {Element} referenceNode Node used as reference to insert after it.
|
|
96
|
+
*/
|
|
97
|
+
function insertAfter(newNode, referenceNode) {
|
|
98
|
+
if (!referenceNode.parentNode) {
|
|
99
|
+
referenceNode = AFRAME.INSPECTOR.selectedEntity;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (!referenceNode) {
|
|
103
|
+
AFRAME.INSPECTOR.sceneEl.appendChild(newNode);
|
|
104
|
+
} else {
|
|
105
|
+
referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Clone an entity, inserting it after the cloned one.
|
|
111
|
+
* @param {Element} entity Entity to clone
|
|
112
|
+
*/
|
|
113
|
+
export function cloneEntity(entity) {
|
|
114
|
+
entity.flushToDOM();
|
|
115
|
+
|
|
116
|
+
const clone = prepareForSerialization(entity);
|
|
117
|
+
clone.addEventListener(
|
|
118
|
+
'loaded',
|
|
119
|
+
function () {
|
|
120
|
+
AFRAME.INSPECTOR.selectEntity(clone);
|
|
121
|
+
Events.emit('entityclone', clone);
|
|
122
|
+
},
|
|
123
|
+
{ once: true }
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
// Get a valid unique ID for the entity
|
|
127
|
+
if (entity.id) {
|
|
128
|
+
clone.id = getUniqueId(entity.id);
|
|
129
|
+
}
|
|
130
|
+
insertAfter(clone, entity);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Clone the selected entity
|
|
135
|
+
*/
|
|
136
|
+
export function cloneSelectedEntity() {
|
|
137
|
+
if (AFRAME.INSPECTOR.selectedEntity) {
|
|
138
|
+
cloneEntity(AFRAME.INSPECTOR.selectedEntity);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Return the clipboard representation to be used to copy to the clipboard
|
|
144
|
+
* @param {Element} entity Entity to copy to clipboard
|
|
145
|
+
* @return {string} Entity clipboard representation
|
|
146
|
+
*/
|
|
147
|
+
export function getEntityClipboardRepresentation(entity) {
|
|
148
|
+
var clone = prepareForSerialization(entity);
|
|
149
|
+
return clone.outerHTML;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Returns a copy of the DOM hierarchy prepared for serialization.
|
|
154
|
+
* The process optimises component representation to avoid values coming from
|
|
155
|
+
* primitive attributes, mixins and defaults.
|
|
156
|
+
*
|
|
157
|
+
* @param {Element} entity Root of the DOM hierarchy.
|
|
158
|
+
* @return {Element} Copy of the DOM hierarchy ready for serialization.
|
|
159
|
+
*/
|
|
160
|
+
function prepareForSerialization(entity) {
|
|
161
|
+
var clone = entity.cloneNode(false);
|
|
162
|
+
var children = entity.childNodes;
|
|
163
|
+
for (var i = 0, l = children.length; i < l; i++) {
|
|
164
|
+
var child = children[i];
|
|
165
|
+
if (
|
|
166
|
+
child.nodeType !== Node.ELEMENT_NODE ||
|
|
167
|
+
(!child.hasAttribute('aframe-injected') &&
|
|
168
|
+
!child.hasAttribute('data-aframe-inspector') &&
|
|
169
|
+
!child.hasAttribute('data-aframe-canvas'))
|
|
170
|
+
) {
|
|
171
|
+
clone.appendChild(prepareForSerialization(children[i]));
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
optimizeComponents(clone, entity);
|
|
175
|
+
return clone;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Removes from copy those components or components' properties that comes from
|
|
180
|
+
* primitive attributes, mixins, injected default components or schema defaults.
|
|
181
|
+
*
|
|
182
|
+
* @param {Element} copy Destinatary element for the optimization.
|
|
183
|
+
* @param {Element} source Element to be optimized.
|
|
184
|
+
*/
|
|
185
|
+
function optimizeComponents(copy, source) {
|
|
186
|
+
var removeAttribute = HTMLElement.prototype.removeAttribute;
|
|
187
|
+
var setAttribute = HTMLElement.prototype.setAttribute;
|
|
188
|
+
var components = source.components || {};
|
|
189
|
+
Object.keys(components).forEach(function (name) {
|
|
190
|
+
var component = components[name];
|
|
191
|
+
var result = getImplicitValue(component, source);
|
|
192
|
+
var isInherited = result[1];
|
|
193
|
+
var implicitValue = result[0];
|
|
194
|
+
var currentValue = source.getAttribute(name);
|
|
195
|
+
var optimalUpdate = getOptimalUpdate(
|
|
196
|
+
component,
|
|
197
|
+
implicitValue,
|
|
198
|
+
currentValue
|
|
199
|
+
);
|
|
200
|
+
var doesNotNeedUpdate = optimalUpdate === null;
|
|
201
|
+
if (isInherited && doesNotNeedUpdate) {
|
|
202
|
+
removeAttribute.call(copy, name);
|
|
203
|
+
} else {
|
|
204
|
+
var schema = component.schema;
|
|
205
|
+
var value = stringifyComponentValue(schema, optimalUpdate);
|
|
206
|
+
setAttribute.call(copy, name, value);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Remove special components if they use the default value
|
|
210
|
+
if (
|
|
211
|
+
value === '' &&
|
|
212
|
+
(name === 'visible' ||
|
|
213
|
+
name === 'position' ||
|
|
214
|
+
name === 'rotation' ||
|
|
215
|
+
name === 'scale')
|
|
216
|
+
) {
|
|
217
|
+
removeAttribute.call(copy, name);
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* @param {Schema} schema The component schema.
|
|
224
|
+
* @param {any} data The component value.
|
|
225
|
+
* @return {string} The string representation of data according to the
|
|
226
|
+
* passed component's schema.
|
|
227
|
+
*/
|
|
228
|
+
function stringifyComponentValue(schema, data) {
|
|
229
|
+
data = typeof data === 'undefined' ? {} : data;
|
|
230
|
+
if (data === null) {
|
|
231
|
+
return '';
|
|
232
|
+
}
|
|
233
|
+
return (isSingleProperty(schema) ? _single : _multi)();
|
|
234
|
+
|
|
235
|
+
function _single() {
|
|
236
|
+
return schema.stringify(data);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function _multi() {
|
|
240
|
+
var propertyBag = {};
|
|
241
|
+
Object.keys(data).forEach(function (name) {
|
|
242
|
+
if (schema[name]) {
|
|
243
|
+
propertyBag[name] = schema[name].stringify(data[name]);
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
return AFRAME.utils.styleParser.stringify(propertyBag);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Computes the value for a component coming from primitive attributes,
|
|
252
|
+
* mixins, primitive defaults, a-frame default components and schema defaults.
|
|
253
|
+
* In this specific order.
|
|
254
|
+
*
|
|
255
|
+
* In other words, it is the value of the component if the author would have not
|
|
256
|
+
* overridden it explicitly.
|
|
257
|
+
*
|
|
258
|
+
* @param {Component} component Component to calculate the value of.
|
|
259
|
+
* @param {Element} source Element owning the component.
|
|
260
|
+
* @return A pair with the computed value for the component of source and a flag indicating if the component is completely inherited from other sources (`true`) or genuinely owned by the source entity (`false`).
|
|
261
|
+
*/
|
|
262
|
+
function getImplicitValue(component, source) {
|
|
263
|
+
var isInherited = false;
|
|
264
|
+
var value = (isSingleProperty(component.schema) ? _single : _multi)();
|
|
265
|
+
return [value, isInherited];
|
|
266
|
+
|
|
267
|
+
function _single() {
|
|
268
|
+
var value = getMixedValue(component, null, source);
|
|
269
|
+
if (value === undefined) {
|
|
270
|
+
value = getInjectedValue(component, null, source);
|
|
271
|
+
}
|
|
272
|
+
if (value !== undefined) {
|
|
273
|
+
isInherited = true;
|
|
274
|
+
} else {
|
|
275
|
+
value = getDefaultValue(component, null, source);
|
|
276
|
+
}
|
|
277
|
+
if (value !== undefined) {
|
|
278
|
+
// XXX: This assumes parse is idempotent
|
|
279
|
+
return component.schema.parse(value);
|
|
280
|
+
}
|
|
281
|
+
return value;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function _multi() {
|
|
285
|
+
var value;
|
|
286
|
+
|
|
287
|
+
Object.keys(component.schema).forEach(function (propertyName) {
|
|
288
|
+
var propertyValue = getFromAttribute(component, propertyName, source);
|
|
289
|
+
if (propertyValue === undefined) {
|
|
290
|
+
propertyValue = getMixedValue(component, propertyName, source);
|
|
291
|
+
}
|
|
292
|
+
if (propertyValue === undefined) {
|
|
293
|
+
propertyValue = getInjectedValue(component, propertyName, source);
|
|
294
|
+
}
|
|
295
|
+
if (propertyValue !== undefined) {
|
|
296
|
+
isInherited = isInherited || true;
|
|
297
|
+
} else {
|
|
298
|
+
propertyValue = getDefaultValue(component, propertyName, source);
|
|
299
|
+
}
|
|
300
|
+
if (propertyValue !== undefined) {
|
|
301
|
+
var parse = component.schema[propertyName].parse;
|
|
302
|
+
value = value || {};
|
|
303
|
+
// XXX: This assumes parse is idempotent
|
|
304
|
+
value[propertyName] = parse(propertyValue);
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
return value;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Gets the value for the component's property coming from a primitive
|
|
314
|
+
* attribute.
|
|
315
|
+
*
|
|
316
|
+
* Primitives have mappings from attributes to component's properties.
|
|
317
|
+
* The function looks for a present attribute in the source element which
|
|
318
|
+
* maps to the specified component's property.
|
|
319
|
+
*
|
|
320
|
+
* @param {Component} component Component to be found.
|
|
321
|
+
* @param {string} propertyName Component's property to be found.
|
|
322
|
+
* @param {Element} source Element owning the component.
|
|
323
|
+
* @return {any} The value of the component's property coming
|
|
324
|
+
* from the primitive's attribute if any or
|
|
325
|
+
* `undefined`, otherwise.
|
|
326
|
+
*/
|
|
327
|
+
function getFromAttribute(component, propertyName, source) {
|
|
328
|
+
var value;
|
|
329
|
+
var mappings = source.mappings || {};
|
|
330
|
+
var route = component.name + '.' + propertyName;
|
|
331
|
+
var primitiveAttribute = findAttribute(mappings, route);
|
|
332
|
+
if (primitiveAttribute && source.hasAttribute(primitiveAttribute)) {
|
|
333
|
+
value = source.getAttribute(primitiveAttribute);
|
|
334
|
+
}
|
|
335
|
+
return value;
|
|
336
|
+
|
|
337
|
+
function findAttribute(mappings, route) {
|
|
338
|
+
var attributes = Object.keys(mappings);
|
|
339
|
+
for (var i = 0, l = attributes.length; i < l; i++) {
|
|
340
|
+
var attribute = attributes[i];
|
|
341
|
+
if (mappings[attribute] === route) {
|
|
342
|
+
return attribute;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
return undefined;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Gets the value for a component or component's property coming from mixins of
|
|
351
|
+
* an element.
|
|
352
|
+
*
|
|
353
|
+
* If the component or component's property is not provided by mixins, the
|
|
354
|
+
* functions will return `undefined`.
|
|
355
|
+
*
|
|
356
|
+
* @param {Component} component Component to be found.
|
|
357
|
+
* @param {string} [propertyName] If provided, component's property to be
|
|
358
|
+
* found.
|
|
359
|
+
* @param {Element} source Element owning the component.
|
|
360
|
+
* @return The value of the component or components'
|
|
361
|
+
* property coming from mixins of the source.
|
|
362
|
+
*/
|
|
363
|
+
function getMixedValue(component, propertyName, source) {
|
|
364
|
+
var value;
|
|
365
|
+
var reversedMixins = source.mixinEls.reverse();
|
|
366
|
+
for (var i = 0; value === undefined && i < reversedMixins.length; i++) {
|
|
367
|
+
var mixin = reversedMixins[i];
|
|
368
|
+
/* eslint-disable-next-line no-prototype-builtins */
|
|
369
|
+
if (mixin.attributes.hasOwnProperty(component.name)) {
|
|
370
|
+
if (!propertyName) {
|
|
371
|
+
value = mixin.getAttribute(component.name);
|
|
372
|
+
} else {
|
|
373
|
+
value = mixin.getAttribute(component.name)[propertyName];
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
return value;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Gets the value for a component or component's property coming from primitive
|
|
382
|
+
* defaults or a-frame defaults. In this specific order.
|
|
383
|
+
*
|
|
384
|
+
* @param {Component} component Component to be found.
|
|
385
|
+
* @param {string} [propertyName] If provided, component's property to be
|
|
386
|
+
* found.
|
|
387
|
+
* @param {Element} source Element owning the component.
|
|
388
|
+
* @return The component value coming from the
|
|
389
|
+
* injected default components of source.
|
|
390
|
+
*/
|
|
391
|
+
function getInjectedValue(component, propertyName, source) {
|
|
392
|
+
var value;
|
|
393
|
+
var primitiveDefaults = source.defaultComponentsFromPrimitive || {};
|
|
394
|
+
var aFrameDefaults = source.defaultComponents || {};
|
|
395
|
+
var defaultSources = [primitiveDefaults, aFrameDefaults];
|
|
396
|
+
for (var i = 0; value === undefined && i < defaultSources.length; i++) {
|
|
397
|
+
var defaults = defaultSources[i];
|
|
398
|
+
/* eslint-disable-next-line no-prototype-builtins */
|
|
399
|
+
if (defaults.hasOwnProperty(component.name)) {
|
|
400
|
+
if (!propertyName) {
|
|
401
|
+
value = defaults[component.name];
|
|
402
|
+
} else {
|
|
403
|
+
value = defaults[component.name][propertyName];
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
return value;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Gets the value for a component or component's property coming from schema
|
|
412
|
+
* defaults.
|
|
413
|
+
*
|
|
414
|
+
* @param {Component} component Component to be found.
|
|
415
|
+
* @param {string} [propertyName] If provided, component's property to be
|
|
416
|
+
* found.
|
|
417
|
+
* @param {Element} source Element owning the component.
|
|
418
|
+
* @return The component value coming from the schema
|
|
419
|
+
* default.
|
|
420
|
+
*/
|
|
421
|
+
function getDefaultValue(component, propertyName, source) {
|
|
422
|
+
if (!propertyName) {
|
|
423
|
+
return component.schema.default;
|
|
424
|
+
}
|
|
425
|
+
return component.schema[propertyName].default;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Returns the minimum value for a component with an implicit value to equal a
|
|
430
|
+
* reference value. A `null` optimal value means that there is no need for an
|
|
431
|
+
* update since the implicit value and the reference are equal.
|
|
432
|
+
*
|
|
433
|
+
* @param {Component} component Component of the computed value.
|
|
434
|
+
* @param {any} implicit The implicit value of the component.
|
|
435
|
+
* @param {any} reference The reference value for the component.
|
|
436
|
+
* @return the minimum value making the component to equal
|
|
437
|
+
* the reference value.
|
|
438
|
+
*/
|
|
439
|
+
function getOptimalUpdate(component, implicit, reference) {
|
|
440
|
+
if (equal(implicit, reference)) {
|
|
441
|
+
return null;
|
|
442
|
+
}
|
|
443
|
+
if (isSingleProperty(component.schema)) {
|
|
444
|
+
return reference;
|
|
445
|
+
}
|
|
446
|
+
var optimal = {};
|
|
447
|
+
Object.keys(reference).forEach(function (key) {
|
|
448
|
+
var needsUpdate = !equal(reference[key], implicit[key]);
|
|
449
|
+
if (needsUpdate) {
|
|
450
|
+
optimal[key] = reference[key];
|
|
451
|
+
}
|
|
452
|
+
});
|
|
453
|
+
return optimal;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* @param {Schema} schema Component's schema to test if it is single property.
|
|
458
|
+
* @return `true` if component is single property.
|
|
459
|
+
*/
|
|
460
|
+
function isSingleProperty(schema) {
|
|
461
|
+
return AFRAME.schema.isSingleProperty(schema);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* Detect element's Id collision and returns a valid one
|
|
466
|
+
* @param {string} baseId Proposed Id
|
|
467
|
+
* @return {string} Valid Id based on the proposed Id
|
|
468
|
+
*/
|
|
469
|
+
function getUniqueId(baseId) {
|
|
470
|
+
if (!document.getElementById(baseId)) {
|
|
471
|
+
return baseId;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
var i = 2;
|
|
475
|
+
// If the baseId ends with _#, it extracts the baseId removing the suffix
|
|
476
|
+
var groups = baseId.match(/(\w+)-(\d+)/);
|
|
477
|
+
if (groups) {
|
|
478
|
+
baseId = groups[1];
|
|
479
|
+
i = groups[2];
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
while (document.getElementById(baseId + '-' + i)) {
|
|
483
|
+
i++;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
return baseId + '-' + i;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
export function getComponentClipboardRepresentation(entity, componentName) {
|
|
490
|
+
/**
|
|
491
|
+
* Get the list of modified properties
|
|
492
|
+
* @param {Element} entity Entity where the component belongs
|
|
493
|
+
* @param {string} componentName Component name
|
|
494
|
+
* @return {object} List of modified properties with their value
|
|
495
|
+
*/
|
|
496
|
+
function getModifiedProperties(entity, componentName) {
|
|
497
|
+
var data = entity.components[componentName].data;
|
|
498
|
+
var defaultData = entity.components[componentName].schema;
|
|
499
|
+
var diff = {};
|
|
500
|
+
for (var key in data) {
|
|
501
|
+
// Prevent adding unknown attributes
|
|
502
|
+
if (!defaultData[key]) {
|
|
503
|
+
continue;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
var defaultValue = defaultData[key].default;
|
|
507
|
+
var currentValue = data[key];
|
|
508
|
+
|
|
509
|
+
// Some parameters could be null and '' like mergeTo
|
|
510
|
+
if ((currentValue || defaultValue) && currentValue !== defaultValue) {
|
|
511
|
+
diff[key] = data[key];
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
return diff;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
const diff = getModifiedProperties(entity, componentName);
|
|
518
|
+
const attributes = AFRAME.utils.styleParser.stringify(diff);
|
|
519
|
+
return `${componentName}="${attributes}"`;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* Helper function to add a new entity with a list of components
|
|
524
|
+
* @param {object} definition Entity definition to add, only components is required:
|
|
525
|
+
* {element: 'a-entity', id: "hbiuSdYL2", class: "box", components: {geometry: 'primitive:box'}}
|
|
526
|
+
* @return {Element} Entity created
|
|
527
|
+
*/
|
|
528
|
+
export function createEntity(definition, cb) {
|
|
529
|
+
const entity = document.createElement(definition.element || 'a-entity');
|
|
530
|
+
if (definition.id) {
|
|
531
|
+
entity.id = definition.id;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
if (definition.class) {
|
|
535
|
+
entity.setAttribute('class', definition.class);
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// load default attributes
|
|
539
|
+
for (let attr in definition.components) {
|
|
540
|
+
entity.setAttribute(attr, definition.components[attr]);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// Ensure the components are loaded before update the UI
|
|
544
|
+
entity.addEventListener(
|
|
545
|
+
'loaded',
|
|
546
|
+
() => {
|
|
547
|
+
Events.emit('entitycreated', entity);
|
|
548
|
+
cb(entity);
|
|
549
|
+
},
|
|
550
|
+
{ once: true }
|
|
551
|
+
);
|
|
552
|
+
|
|
553
|
+
AFRAME.scenes[0].appendChild(entity);
|
|
554
|
+
|
|
555
|
+
return entity;
|
|
556
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import Events from './Events';
|
|
2
|
+
|
|
3
|
+
export const updates = {};
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Store change to export.
|
|
7
|
+
*
|
|
8
|
+
* payload: entity, component, property, value.
|
|
9
|
+
*/
|
|
10
|
+
Events.on('entityupdate', (payload) => {
|
|
11
|
+
let value = payload.value;
|
|
12
|
+
|
|
13
|
+
const entity = payload.entity;
|
|
14
|
+
updates[entity.id] = updates[entity.id] || {};
|
|
15
|
+
|
|
16
|
+
const component = AFRAME.components[payload.component];
|
|
17
|
+
if (component) {
|
|
18
|
+
if (payload.property) {
|
|
19
|
+
updates[entity.id][payload.component] =
|
|
20
|
+
updates[entity.id][payload.component] || {};
|
|
21
|
+
if (component.schema[payload.property]) {
|
|
22
|
+
value = component.schema[payload.property].stringify(payload.value);
|
|
23
|
+
}
|
|
24
|
+
updates[entity.id][payload.component][payload.property] = value;
|
|
25
|
+
} else {
|
|
26
|
+
value = component.schema.stringify(payload.value);
|
|
27
|
+
updates[entity.id][payload.component] = value;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
});
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import Events from './Events';
|
|
2
|
+
|
|
3
|
+
export function initRaycaster(inspector) {
|
|
4
|
+
// Use cursor="rayOrigin: mouse".
|
|
5
|
+
const mouseCursor = document.createElement('a-entity');
|
|
6
|
+
mouseCursor.setAttribute('id', 'aframeInspectorMouseCursor');
|
|
7
|
+
mouseCursor.setAttribute('raycaster', {
|
|
8
|
+
interval: 100,
|
|
9
|
+
objects: 'a-scene :not([data-aframe-inspector])'
|
|
10
|
+
});
|
|
11
|
+
mouseCursor.setAttribute('cursor', 'rayOrigin', 'mouse');
|
|
12
|
+
mouseCursor.setAttribute('data-aframe-inspector', 'true');
|
|
13
|
+
|
|
14
|
+
// Only visible objects.
|
|
15
|
+
const raycaster = mouseCursor.components.raycaster;
|
|
16
|
+
const refreshObjects = raycaster.refreshObjects;
|
|
17
|
+
const overrideRefresh = () => {
|
|
18
|
+
refreshObjects.call(raycaster);
|
|
19
|
+
const objects = raycaster.objects;
|
|
20
|
+
raycaster.objects = objects.filter((node) => {
|
|
21
|
+
while (node) {
|
|
22
|
+
if (!node.visible) {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
node = node.parent;
|
|
26
|
+
}
|
|
27
|
+
return true;
|
|
28
|
+
});
|
|
29
|
+
};
|
|
30
|
+
raycaster.refreshObjects = overrideRefresh;
|
|
31
|
+
|
|
32
|
+
inspector.sceneEl.appendChild(mouseCursor);
|
|
33
|
+
inspector.cursor = mouseCursor;
|
|
34
|
+
|
|
35
|
+
mouseCursor.addEventListener('click', handleClick);
|
|
36
|
+
mouseCursor.addEventListener('mouseenter', onMouseEnter);
|
|
37
|
+
mouseCursor.addEventListener('mouseleave', onMouseLeave);
|
|
38
|
+
inspector.container.addEventListener('mousedown', onMouseDown);
|
|
39
|
+
inspector.container.addEventListener('mouseup', onMouseUp);
|
|
40
|
+
inspector.container.addEventListener('dblclick', onDoubleClick);
|
|
41
|
+
|
|
42
|
+
inspector.sceneEl.canvas.addEventListener('mouseleave', () => {
|
|
43
|
+
setTimeout(() => {
|
|
44
|
+
Events.emit('raycastermouseleave', null);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const onDownPosition = new THREE.Vector2();
|
|
49
|
+
const onUpPosition = new THREE.Vector2();
|
|
50
|
+
|
|
51
|
+
function onMouseEnter() {
|
|
52
|
+
Events.emit(
|
|
53
|
+
'raycastermouseenter',
|
|
54
|
+
mouseCursor.components.cursor.intersectedEl
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function onMouseLeave() {
|
|
59
|
+
Events.emit(
|
|
60
|
+
'raycastermouseleave',
|
|
61
|
+
mouseCursor.components.cursor.intersectedEl
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function handleClick(evt) {
|
|
66
|
+
// Check to make sure not dragging.
|
|
67
|
+
if (onDownPosition.distanceTo(onUpPosition) === 0) {
|
|
68
|
+
inspector.selectEntity(evt.detail.intersectedEl);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function onMouseDown(event) {
|
|
73
|
+
if (event instanceof CustomEvent) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
event.preventDefault();
|
|
77
|
+
const array = getMousePosition(
|
|
78
|
+
inspector.container,
|
|
79
|
+
event.clientX,
|
|
80
|
+
event.clientY
|
|
81
|
+
);
|
|
82
|
+
onDownPosition.fromArray(array);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function onMouseUp(event) {
|
|
86
|
+
if (event instanceof CustomEvent) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
event.preventDefault();
|
|
90
|
+
const array = getMousePosition(
|
|
91
|
+
inspector.container,
|
|
92
|
+
event.clientX,
|
|
93
|
+
event.clientY
|
|
94
|
+
);
|
|
95
|
+
onUpPosition.fromArray(array);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Focus on double click.
|
|
100
|
+
*/
|
|
101
|
+
function onDoubleClick(event) {
|
|
102
|
+
const intersectedEl = mouseCursor.components.cursor.intersectedEl;
|
|
103
|
+
if (!intersectedEl) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
Events.emit('objectfocus', intersectedEl.object3D);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
el: mouseCursor,
|
|
111
|
+
enable: () => {
|
|
112
|
+
mouseCursor.setAttribute('raycaster', 'enabled', true);
|
|
113
|
+
inspector.container.addEventListener('mousedown', onMouseDown);
|
|
114
|
+
inspector.container.addEventListener('mouseup', onMouseUp);
|
|
115
|
+
inspector.container.addEventListener('dblclick', onDoubleClick);
|
|
116
|
+
},
|
|
117
|
+
disable: () => {
|
|
118
|
+
mouseCursor.setAttribute('raycaster', 'enabled', false);
|
|
119
|
+
inspector.container.removeEventListener('mousedown', onMouseDown);
|
|
120
|
+
inspector.container.removeEventListener('mouseup', onMouseUp);
|
|
121
|
+
inspector.container.removeEventListener('dblclick', onDoubleClick);
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function getMousePosition(dom, x, y) {
|
|
127
|
+
const rect = dom.getBoundingClientRect();
|
|
128
|
+
return [(x - rect.left) / rect.width, (y - rect.top) / rect.height];
|
|
129
|
+
}
|