overte/scripts/simplifiedUI/system/html/js/entityProperties.js
2019-06-19 13:31:21 -07:00

4419 lines
157 KiB
JavaScript

// entityProperties.js
//
// Created by Ryan Huffman on 13 Nov 2014
// Modified by David Back on 19 Oct 2018
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
/* global alert, augmentSpinButtons, clearTimeout, console, document, Element,
EventBridge, JSONEditor, openEventBridge, setTimeout, window, _, $ */
const DEGREES_TO_RADIANS = Math.PI / 180.0;
const NO_SELECTION = ",";
const PROPERTY_SPACE_MODE = Object.freeze({
ALL: 0,
LOCAL: 1,
WORLD: 2
});
const PROPERTY_SELECTION_VISIBILITY = Object.freeze({
SINGLE_SELECTION: 1,
MULTIPLE_SELECTIONS: 2,
MULTI_DIFF_SELECTIONS: 4,
ANY_SELECTIONS: 7, /* SINGLE_SELECTION | MULTIPLE_SELECTIONS | MULTI_DIFF_SELECTIONS */
});
// Multiple-selection behavior
const PROPERTY_MULTI_DISPLAY_MODE = Object.freeze({
DEFAULT: 0,
/**
* Comma separated values
* Limited for properties with type "string" or "textarea" and readOnly enabled
*/
COMMA_SEPARATED_VALUES: 1,
});
const GROUPS = [
{
id: "base",
properties: [
{
label: NO_SELECTION,
type: "icon",
icons: ENTITY_TYPE_ICON,
propertyID: "type",
replaceID: "placeholder-property-type",
},
{
label: "Name",
type: "string",
propertyID: "name",
placeholder: "Name",
replaceID: "placeholder-property-name",
},
{
label: "ID",
type: "string",
propertyID: "id",
placeholder: "ID",
readOnly: true,
replaceID: "placeholder-property-id",
multiDisplayMode: PROPERTY_MULTI_DISPLAY_MODE.COMMA_SEPARATED_VALUES,
},
{
label: "Description",
type: "string",
propertyID: "description",
},
{
label: "Parent",
type: "string",
propertyID: "parentID",
onChange: parentIDChanged,
},
{
label: "Parent Joint Index",
type: "number",
propertyID: "parentJointIndex",
},
{
label: "",
glyph: "",
type: "bool",
propertyID: "locked",
replaceID: "placeholder-property-locked",
},
{
label: "",
glyph: "",
type: "bool",
propertyID: "visible",
replaceID: "placeholder-property-visible",
},
{
label: "Render Layer",
type: "dropdown",
options: {
world: "World",
front: "Front",
hud: "HUD"
},
propertyID: "renderLayer",
},
{
label: "Primitive Mode",
type: "dropdown",
options: {
solid: "Solid",
lines: "Wireframe",
},
propertyID: "primitiveMode",
},
]
},
{
id: "shape",
addToGroup: "base",
properties: [
{
label: "Shape",
type: "dropdown",
options: { Cube: "Box", Sphere: "Sphere", Tetrahedron: "Tetrahedron", Octahedron: "Octahedron",
Icosahedron: "Icosahedron", Dodecahedron: "Dodecahedron", Hexagon: "Hexagon",
Triangle: "Triangle", Octagon: "Octagon", Cylinder: "Cylinder", Cone: "Cone",
Circle: "Circle", Quad: "Quad" },
propertyID: "shape",
},
{
label: "Color",
type: "color",
propertyID: "color",
},
]
},
{
id: "text",
addToGroup: "base",
properties: [
{
label: "Text",
type: "string",
propertyID: "text",
},
{
label: "Text Color",
type: "color",
propertyID: "textColor",
},
{
label: "Text Alpha",
type: "number-draggable",
min: 0,
max: 1,
step: 0.01,
decimals: 2,
propertyID: "textAlpha",
},
{
label: "Background Color",
type: "color",
propertyID: "backgroundColor",
},
{
label: "Background Alpha",
type: "number-draggable",
min: 0,
max: 1,
step: 0.01,
decimals: 2,
propertyID: "backgroundAlpha",
},
{
label: "Line Height",
type: "number-draggable",
min: 0,
step: 0.001,
decimals: 4,
unit: "m",
propertyID: "lineHeight",
},
{
label: "Billboard Mode",
type: "dropdown",
options: { none: "None", yaw: "Yaw", full: "Full"},
propertyID: "textBillboardMode",
propertyName: "billboardMode", // actual entity property name
},
{
label: "Top Margin",
type: "number-draggable",
step: 0.01,
decimals: 2,
propertyID: "topMargin",
},
{
label: "Right Margin",
type: "number-draggable",
step: 0.01,
decimals: 2,
propertyID: "rightMargin",
},
{
label: "Bottom Margin",
type: "number-draggable",
step: 0.01,
decimals: 2,
propertyID: "bottomMargin",
},
{
label: "Left Margin",
type: "number-draggable",
step: 0.01,
decimals: 2,
propertyID: "leftMargin",
},
]
},
{
id: "zone",
addToGroup: "base",
properties: [
{
label: "Shape Type",
type: "dropdown",
options: { "box": "Box", "sphere": "Sphere", "ellipsoid": "Ellipsoid",
"cylinder-y": "Cylinder", "compound": "Use Compound Shape URL" },
propertyID: "zoneShapeType",
propertyName: "shapeType", // actual entity property name
},
{
label: "Compound Shape URL",
type: "string",
propertyID: "zoneCompoundShapeURL",
propertyName: "compoundShapeURL", // actual entity property name
},
{
label: "Flying Allowed",
type: "bool",
propertyID: "flyingAllowed",
},
{
label: "Ghosting Allowed",
type: "bool",
propertyID: "ghostingAllowed",
},
{
label: "Filter",
type: "string",
propertyID: "filterURL",
},
{
label: "Key Light",
type: "dropdown",
options: { inherit: "Inherit", disabled: "Off", enabled: "On" },
propertyID: "keyLightMode",
},
{
label: "Key Light Color",
type: "color",
propertyID: "keyLight.color",
showPropertyRule: { "keyLightMode": "enabled" },
},
{
label: "Light Intensity",
type: "number-draggable",
min: 0,
max: 40,
step: 0.01,
decimals: 2,
propertyID: "keyLight.intensity",
showPropertyRule: { "keyLightMode": "enabled" },
},
{
label: "Light Horizontal Angle",
type: "number-draggable",
step: 0.1,
multiplier: DEGREES_TO_RADIANS,
decimals: 2,
unit: "deg",
propertyID: "keyLight.direction.y",
showPropertyRule: { "keyLightMode": "enabled" },
},
{
label: "Light Vertical Angle",
type: "number-draggable",
step: 0.1,
multiplier: DEGREES_TO_RADIANS,
decimals: 2,
unit: "deg",
propertyID: "keyLight.direction.x",
showPropertyRule: { "keyLightMode": "enabled" },
},
{
label: "Cast Shadows",
type: "bool",
propertyID: "keyLight.castShadows",
showPropertyRule: { "keyLightMode": "enabled" },
},
{
label: "Skybox",
type: "dropdown",
options: { inherit: "Inherit", disabled: "Off", enabled: "On" },
propertyID: "skyboxMode",
},
{
label: "Skybox Color",
type: "color",
propertyID: "skybox.color",
showPropertyRule: { "skyboxMode": "enabled" },
},
{
label: "Skybox Source",
type: "string",
propertyID: "skybox.url",
showPropertyRule: { "skyboxMode": "enabled" },
},
{
label: "Ambient Light",
type: "dropdown",
options: { inherit: "Inherit", disabled: "Off", enabled: "On" },
propertyID: "ambientLightMode",
},
{
label: "Ambient Intensity",
type: "number-draggable",
min: 0,
max: 200,
step: 0.1,
decimals: 2,
propertyID: "ambientLight.ambientIntensity",
showPropertyRule: { "ambientLightMode": "enabled" },
},
{
label: "Ambient Source",
type: "string",
propertyID: "ambientLight.ambientURL",
showPropertyRule: { "ambientLightMode": "enabled" },
},
{
type: "buttons",
buttons: [ { id: "copy", label: "Copy from Skybox",
className: "black", onClick: copySkyboxURLToAmbientURL } ],
propertyID: "copyURLToAmbient",
showPropertyRule: { "ambientLightMode": "enabled" },
},
{
label: "Haze",
type: "dropdown",
options: { inherit: "Inherit", disabled: "Off", enabled: "On" },
propertyID: "hazeMode",
},
{
label: "Range",
type: "number-draggable",
min: 1,
max: 10000,
step: 1,
decimals: 0,
unit: "m",
propertyID: "haze.hazeRange",
showPropertyRule: { "hazeMode": "enabled" },
},
{
label: "Use Altitude",
type: "bool",
propertyID: "haze.hazeAltitudeEffect",
showPropertyRule: { "hazeMode": "enabled" },
},
{
label: "Base",
type: "number-draggable",
min: -1000,
max: 1000,
step: 1,
decimals: 0,
unit: "m",
propertyID: "haze.hazeBaseRef",
showPropertyRule: { "hazeMode": "enabled" },
},
{
label: "Ceiling",
type: "number-draggable",
min: -1000,
max: 5000,
step: 1,
decimals: 0,
unit: "m",
propertyID: "haze.hazeCeiling",
showPropertyRule: { "hazeMode": "enabled" },
},
{
label: "Haze Color",
type: "color",
propertyID: "haze.hazeColor",
showPropertyRule: { "hazeMode": "enabled" },
},
{
label: "Background Blend",
type: "number-draggable",
min: 0,
max: 1,
step: 0.001,
decimals: 3,
propertyID: "haze.hazeBackgroundBlend",
showPropertyRule: { "hazeMode": "enabled" },
},
{
label: "Enable Glare",
type: "bool",
propertyID: "haze.hazeEnableGlare",
showPropertyRule: { "hazeMode": "enabled" },
},
{
label: "Glare Color",
type: "color",
propertyID: "haze.hazeGlareColor",
showPropertyRule: { "hazeMode": "enabled" },
},
{
label: "Glare Angle",
type: "number-draggable",
min: 0,
max: 180,
step: 1,
decimals: 0,
propertyID: "haze.hazeGlareAngle",
showPropertyRule: { "hazeMode": "enabled" },
},
{
label: "Bloom",
type: "dropdown",
options: { inherit: "Inherit", disabled: "Off", enabled: "On" },
propertyID: "bloomMode",
},
{
label: "Bloom Intensity",
type: "number-draggable",
min: 0,
max: 1,
step: 0.001,
decimals: 3,
propertyID: "bloom.bloomIntensity",
showPropertyRule: { "bloomMode": "enabled" },
},
{
label: "Bloom Threshold",
type: "number-draggable",
min: 0,
max: 1,
step: 0.001,
decimals: 3,
propertyID: "bloom.bloomThreshold",
showPropertyRule: { "bloomMode": "enabled" },
},
{
label: "Bloom Size",
type: "number-draggable",
min: 0,
max: 2,
step: 0.001,
decimals: 3,
propertyID: "bloom.bloomSize",
showPropertyRule: { "bloomMode": "enabled" },
},
{
label: "Avatar Priority",
type: "dropdown",
options: { inherit: "Inherit", crowd: "Crowd", hero: "Hero" },
propertyID: "avatarPriority",
},
]
},
{
id: "model",
addToGroup: "base",
properties: [
{
label: "Model",
type: "string",
placeholder: "URL",
propertyID: "modelURL",
hideIfCertified: true,
},
{
label: "Collision Shape",
type: "dropdown",
options: { "none": "No Collision", "box": "Box", "sphere": "Sphere", "compound": "Compound" ,
"simple-hull": "Basic - Whole model", "simple-compound": "Good - Sub-meshes" ,
"static-mesh": "Exact - All polygons (non-dynamic only)" },
propertyID: "shapeType",
},
{
label: "Compound Shape",
type: "string",
propertyID: "compoundShapeURL",
hideIfCertified: true,
},
{
label: "Animation",
type: "string",
propertyID: "animation.url",
hideIfCertified: true,
},
{
label: "Play Automatically",
type: "bool",
propertyID: "animation.running",
},
{
label: "Loop",
type: "bool",
propertyID: "animation.loop",
},
{
label: "Allow Transition",
type: "bool",
propertyID: "animation.allowTranslation",
},
{
label: "Hold",
type: "bool",
propertyID: "animation.hold",
},
{
label: "Animation Frame",
type: "number-draggable",
propertyID: "animation.currentFrame",
},
{
label: "First Frame",
type: "number-draggable",
propertyID: "animation.firstFrame",
},
{
label: "Last Frame",
type: "number-draggable",
propertyID: "animation.lastFrame",
},
{
label: "Animation FPS",
type: "number-draggable",
propertyID: "animation.fps",
},
{
label: "Texture",
type: "textarea",
propertyID: "textures",
},
{
label: "Original Texture",
type: "textarea",
propertyID: "originalTextures",
readOnly: true,
hideIfCertified: true,
},
{
label: "Group Culled",
type: "bool",
propertyID: "groupCulled",
},
]
},
{
id: "image",
addToGroup: "base",
properties: [
{
label: "Image",
type: "string",
placeholder: "URL",
propertyID: "imageURL",
},
{
label: "Color",
type: "color",
propertyID: "imageColor",
propertyName: "color", // actual entity property name
},
{
label: "Emissive",
type: "bool",
propertyID: "emissive",
},
{
label: "Sub Image",
type: "rect",
min: 0,
step: 1,
subLabels: [ "x", "y", "w", "h" ],
propertyID: "subImage",
},
{
label: "Billboard Mode",
type: "dropdown",
options: { none: "None", yaw: "Yaw", full: "Full"},
propertyID: "imageBillboardMode",
propertyName: "billboardMode", // actual entity property name
},
{
label: "Keep Aspect Ratio",
type: "bool",
propertyID: "keepAspectRatio",
},
]
},
{
id: "web",
addToGroup: "base",
properties: [
{
label: "Source",
type: "string",
propertyID: "sourceUrl",
},
{
label: "Source Resolution",
type: "number-draggable",
propertyID: "dpi",
},
{
label: "Web Color",
type: "color",
propertyID: "webColor",
propertyName: "color", // actual entity property name
},
{
label: "Web Alpha",
type: "number-draggable",
step: 0.001,
decimals: 3,
propertyID: "webAlpha",
propertyName: "alpha",
min: 0,
max: 1,
},
{
label: "Max FPS",
type: "number-draggable",
step: 1,
decimals: 0,
propertyID: "maxFPS",
},
{
label: "Script URL",
type: "string",
propertyID: "scriptURL",
placeholder: "URL",
},
]
},
{
id: "light",
addToGroup: "base",
properties: [
{
label: "Light Color",
type: "color",
propertyID: "lightColor",
propertyName: "color", // actual entity property name
},
{
label: "Intensity",
type: "number-draggable",
min: 0,
max: 10000,
step: 0.1,
decimals: 2,
propertyID: "intensity",
},
{
label: "Fall-Off Radius",
type: "number-draggable",
min: 0,
max: 10000,
step: 0.1,
decimals: 2,
unit: "m",
propertyID: "falloffRadius",
},
{
label: "Spotlight",
type: "bool",
propertyID: "isSpotlight",
},
{
label: "Spotlight Exponent",
type: "number-draggable",
min: 0,
step: 0.01,
decimals: 2,
propertyID: "exponent",
},
{
label: "Spotlight Cut-Off",
type: "number-draggable",
step: 0.01,
decimals: 2,
propertyID: "cutoff",
},
]
},
{
id: "material",
addToGroup: "base",
properties: [
{
label: "Material URL",
type: "string",
propertyID: "materialURL",
},
{
label: "Material Data",
type: "textarea",
buttons: [ { id: "clear", label: "Clear Material Data", className: "red", onClick: clearMaterialData },
{ id: "edit", label: "Edit as JSON", className: "blue", onClick: newJSONMaterialEditor },
{ id: "save", label: "Save Material Data", className: "black", onClick: saveMaterialData } ],
propertyID: "materialData",
},
{
label: "Material Target",
type: "dynamic-multiselect",
propertyUpdate: materialTargetPropertyUpdate,
propertyID: "parentMaterialName",
selectionVisibility: PROPERTY_SELECTION_VISIBILITY.SINGLE_SELECTION,
},
{
label: "Priority",
type: "number-draggable",
min: 0,
propertyID: "priority",
},
{
label: "Material Mapping Mode",
type: "dropdown",
options: {
uv: "UV space", projected: "3D projected"
},
propertyID: "materialMappingMode",
},
{
label: "Material Position",
type: "vec2",
vec2Type: "xyz",
min: 0,
max: 1,
step: 0.1,
decimals: 4,
subLabels: [ "x", "y" ],
propertyID: "materialMappingPos",
},
{
label: "Material Scale",
type: "vec2",
vec2Type: "xyz",
min: 0,
step: 0.1,
decimals: 4,
subLabels: [ "x", "y" ],
propertyID: "materialMappingScale",
},
{
label: "Material Rotation",
type: "number-draggable",
step: 0.1,
decimals: 2,
unit: "deg",
propertyID: "materialMappingRot",
},
{
label: "Material Repeat",
type: "bool",
propertyID: "materialRepeat",
},
]
},
{
id: "grid",
addToGroup: "base",
properties: [
{
label: "Color",
type: "color",
propertyID: "gridColor",
propertyName: "color", // actual entity property name
},
{
label: "Follow Camera",
type: "bool",
propertyID: "followCamera",
},
{
label: "Major Grid Every",
type: "number-draggable",
min: 0,
step: 1,
decimals: 0,
propertyID: "majorGridEvery",
},
{
label: "Minor Grid Every",
type: "number-draggable",
min: 0,
step: 0.01,
decimals: 2,
propertyID: "minorGridEvery",
},
]
},
{
id: "particles",
addToGroup: "base",
properties: [
{
label: "Emit",
type: "bool",
propertyID: "isEmitting",
},
{
label: "Lifespan",
type: "number-draggable",
unit: "s",
step: 0.01,
decimals: 2,
propertyID: "lifespan",
},
{
label: "Max Particles",
type: "number-draggable",
step: 1,
propertyID: "maxParticles",
},
{
label: "Texture",
type: "texture",
propertyID: "particleTextures",
propertyName: "textures", // actual entity property name
},
]
},
{
id: "particles_emit",
label: "EMIT",
isMinor: true,
properties: [
{
label: "Emit Rate",
type: "number-draggable",
step: 1,
propertyID: "emitRate",
},
{
label: "Emit Speed",
type: "number-draggable",
step: 0.1,
decimals: 2,
propertyID: "emitSpeed",
},
{
label: "Speed Spread",
type: "number-draggable",
step: 0.1,
decimals: 2,
propertyID: "speedSpread",
},
{
label: "Shape Type",
type: "dropdown",
options: { "box": "Box", "ellipsoid": "Ellipsoid",
"cylinder-y": "Cylinder", "circle": "Circle", "plane": "Plane",
"compound": "Use Compound Shape URL" },
propertyID: "particleShapeType",
propertyName: "shapeType",
},
{
label: "Compound Shape URL",
type: "string",
propertyID: "particleCompoundShapeURL",
propertyName: "compoundShapeURL",
},
{
label: "Emit Dimensions",
type: "vec3",
vec3Type: "xyz",
step: 0.01,
round: 100,
subLabels: [ "x", "y", "z" ],
propertyID: "emitDimensions",
},
{
label: "Emit Radius Start",
type: "number-draggable",
step: 0.001,
decimals: 3,
propertyID: "emitRadiusStart"
},
{
label: "Emit Orientation",
type: "vec3",
vec3Type: "pyr",
step: 0.01,
round: 100,
subLabels: [ "x", "y", "z" ],
unit: "deg",
propertyID: "emitOrientation",
},
{
label: "Trails",
type: "bool",
propertyID: "emitterShouldTrail",
},
]
},
{
id: "particles_size",
label: "SIZE",
isMinor: true,
properties: [
{
type: "triple",
label: "Size",
propertyID: "particleRadiusTriple",
properties: [
{
label: "Start",
type: "number-draggable",
step: 0.01,
decimals: 2,
propertyID: "radiusStart",
fallbackProperty: "particleRadius",
},
{
label: "Middle",
type: "number-draggable",
step: 0.01,
decimals: 2,
propertyID: "particleRadius",
},
{
label: "Finish",
type: "number-draggable",
step: 0.01,
decimals: 2,
propertyID: "radiusFinish",
fallbackProperty: "particleRadius",
},
]
},
{
label: "Size Spread",
type: "number-draggable",
step: 0.01,
decimals: 2,
propertyID: "radiusSpread",
},
]
},
{
id: "particles_color",
label: "COLOR",
isMinor: true,
properties: [
{
type: "triple",
label: "Color",
propertyID: "particleColorTriple",
properties: [
{
label: "Start",
type: "color",
propertyID: "colorStart",
fallbackProperty: "color",
},
{
label: "Middle",
type: "color",
propertyID: "particleColor",
propertyName: "color", // actual entity property name
},
{
label: "Finish",
type: "color",
propertyID: "colorFinish",
fallbackProperty: "color",
},
]
},
{
label: "Color Spread",
type: "color",
propertyID: "colorSpread",
},
]
},
{
id: "particles_alpha",
label: "ALPHA",
isMinor: true,
properties: [
{
type: "triple",
label: "Alpha",
propertyID: "particleAlphaTriple",
properties: [
{
label: "Start",
type: "number-draggable",
step: 0.001,
decimals: 3,
propertyID: "alphaStart",
fallbackProperty: "alpha",
},
{
label: "Middle",
type: "number-draggable",
step: 0.001,
decimals: 3,
propertyID: "alpha",
},
{
label: "Finish",
type: "number-draggable",
step: 0.001,
decimals: 3,
propertyID: "alphaFinish",
fallbackProperty: "alpha",
},
]
},
{
label: "Alpha Spread",
type: "number-draggable",
step: 0.001,
decimals: 3,
propertyID: "alphaSpread",
},
]
},
{
id: "particles_acceleration",
label: "ACCELERATION",
isMinor: true,
properties: [
{
label: "Emit Acceleration",
type: "vec3",
vec3Type: "xyz",
step: 0.01,
round: 100,
subLabels: [ "x", "y", "z" ],
propertyID: "emitAcceleration",
},
{
label: "Acceleration Spread",
type: "vec3",
vec3Type: "xyz",
step: 0.01,
round: 100,
subLabels: [ "x", "y", "z" ],
propertyID: "accelerationSpread",
},
]
},
{
id: "particles_spin",
label: "SPIN",
isMinor: true,
properties: [
{
type: "triple",
label: "Spin",
propertyID: "particleSpinTriple",
properties: [
{
label: "Start",
type: "number-draggable",
step: 0.1,
decimals: 2,
multiplier: DEGREES_TO_RADIANS,
unit: "deg",
propertyID: "spinStart",
fallbackProperty: "particleSpin",
},
{
label: "Middle",
type: "number-draggable",
step: 0.1,
decimals: 2,
multiplier: DEGREES_TO_RADIANS,
unit: "deg",
propertyID: "particleSpin",
},
{
label: "Finish",
type: "number-draggable",
step: 0.1,
decimals: 2,
multiplier: DEGREES_TO_RADIANS,
unit: "deg",
propertyID: "spinFinish",
fallbackProperty: "particleSpin",
},
]
},
{
label: "Spin Spread",
type: "number-draggable",
step: 0.1,
decimals: 2,
multiplier: DEGREES_TO_RADIANS,
unit: "deg",
propertyID: "spinSpread",
},
{
label: "Rotate with Entity",
type: "bool",
propertyID: "rotateWithEntity",
},
]
},
{
id: "particles_constraints",
label: "CONSTRAINTS",
isMinor: true,
properties: [
{
type: "triple",
label: "Horizontal Angle",
propertyID: "particlePolarTriple",
properties: [
{
label: "Start",
type: "number-draggable",
step: 0.1,
decimals: 2,
multiplier: DEGREES_TO_RADIANS,
unit: "deg",
propertyID: "polarStart",
},
{
label: "Finish",
type: "number-draggable",
step: 0.1,
decimals: 2,
multiplier: DEGREES_TO_RADIANS,
unit: "deg",
propertyID: "polarFinish",
},
],
},
{
type: "triple",
label: "Vertical Angle",
propertyID: "particleAzimuthTriple",
properties: [
{
label: "Start",
type: "number-draggable",
step: 0.1,
decimals: 2,
multiplier: DEGREES_TO_RADIANS,
unit: "deg",
propertyID: "azimuthStart",
},
{
label: "Finish",
type: "number-draggable",
step: 0.1,
decimals: 2,
multiplier: DEGREES_TO_RADIANS,
unit: "deg",
propertyID: "azimuthFinish",
},
]
}
]
},
{
id: "spatial",
label: "SPATIAL",
properties: [
{
label: "Position",
type: "vec3",
vec3Type: "xyz",
step: 0.1,
decimals: 4,
subLabels: [ "x", "y", "z" ],
unit: "m",
propertyID: "position",
spaceMode: PROPERTY_SPACE_MODE.WORLD,
},
{
label: "Local Position",
type: "vec3",
vec3Type: "xyz",
step: 0.1,
decimals: 4,
subLabels: [ "x", "y", "z" ],
unit: "m",
propertyID: "localPosition",
spaceMode: PROPERTY_SPACE_MODE.LOCAL,
},
{
label: "Rotation",
type: "vec3",
vec3Type: "pyr",
step: 0.1,
decimals: 4,
subLabels: [ "x", "y", "z" ],
unit: "deg",
propertyID: "rotation",
spaceMode: PROPERTY_SPACE_MODE.WORLD,
},
{
label: "Local Rotation",
type: "vec3",
vec3Type: "pyr",
step: 0.1,
decimals: 4,
subLabels: [ "x", "y", "z" ],
unit: "deg",
propertyID: "localRotation",
spaceMode: PROPERTY_SPACE_MODE.LOCAL,
},
{
label: "Dimensions",
type: "vec3",
vec3Type: "xyz",
step: 0.01,
decimals: 4,
subLabels: [ "x", "y", "z" ],
unit: "m",
propertyID: "dimensions",
spaceMode: PROPERTY_SPACE_MODE.WORLD,
},
{
label: "Local Dimensions",
type: "vec3",
vec3Type: "xyz",
step: 0.01,
decimals: 4,
subLabels: [ "x", "y", "z" ],
unit: "m",
propertyID: "localDimensions",
spaceMode: PROPERTY_SPACE_MODE.LOCAL,
},
{
label: "Scale",
type: "number-draggable",
defaultValue: 100,
unit: "%",
buttons: [ { id: "rescale", label: "Rescale", className: "blue", onClick: rescaleDimensions },
{ id: "reset", label: "Reset Dimensions", className: "red", onClick: resetToNaturalDimensions } ],
propertyID: "scale",
},
{
label: "Pivot",
type: "vec3",
vec3Type: "xyz",
step: 0.001,
decimals: 4,
subLabels: [ "x", "y", "z" ],
unit: "(ratio of dimension)",
propertyID: "registrationPoint",
},
{
label: "Align",
type: "buttons",
buttons: [ { id: "selection", label: "Selection to Grid", className: "black", onClick: moveSelectionToGrid },
{ id: "all", label: "All to Grid", className: "black", onClick: moveAllToGrid } ],
propertyID: "alignToGrid",
},
]
},
{
id: "behavior",
label: "BEHAVIOR",
properties: [
{
label: "Grabbable",
type: "bool",
propertyID: "grab.grabbable",
},
{
label: "Cloneable",
type: "bool",
propertyID: "cloneable",
},
{
label: "Clone Lifetime",
type: "number-draggable",
min: -1,
unit: "s",
propertyID: "cloneLifetime",
showPropertyRule: { "cloneable": "true" },
},
{
label: "Clone Limit",
type: "number-draggable",
min: 0,
propertyID: "cloneLimit",
showPropertyRule: { "cloneable": "true" },
},
{
label: "Clone Dynamic",
type: "bool",
propertyID: "cloneDynamic",
showPropertyRule: { "cloneable": "true" },
},
{
label: "Clone Avatar Entity",
type: "bool",
propertyID: "cloneAvatarEntity",
showPropertyRule: { "cloneable": "true" },
},
{
label: "Triggerable",
type: "bool",
propertyID: "grab.triggerable",
},
{
label: "Follow Controller",
type: "bool",
propertyID: "grab.grabFollowsController",
},
{
label: "Cast Shadows",
type: "bool",
propertyID: "canCastShadow",
},
{
label: "Link",
type: "string",
propertyID: "href",
placeholder: "URL",
},
{
label: "Ignore Pick Intersection",
type: "bool",
propertyID: "ignorePickIntersection",
},
{
label: "Script",
type: "string",
buttons: [ { id: "reload", label: "F", className: "glyph", onClick: reloadScripts } ],
propertyID: "script",
placeholder: "URL",
hideIfCertified: true,
},
{
label: "Server Script",
type: "string",
buttons: [ { id: "reload", label: "F", className: "glyph", onClick: reloadServerScripts } ],
propertyID: "serverScripts",
placeholder: "URL",
},
{
label: "Server Script Status",
type: "placeholder",
indentedLabel: true,
propertyID: "serverScriptStatus",
selectionVisibility: PROPERTY_SELECTION_VISIBILITY.SINGLE_SELECTION,
},
{
label: "Lifetime",
type: "number",
unit: "s",
propertyID: "lifetime",
},
{
label: "User Data",
type: "textarea",
buttons: [ { id: "clear", label: "Clear User Data", className: "red", onClick: clearUserData },
{ id: "edit", label: "Edit as JSON", className: "blue", onClick: newJSONEditor },
{ id: "save", label: "Save User Data", className: "black", onClick: saveUserData } ],
propertyID: "userData",
},
]
},
{
id: "collision",
label: "COLLISION",
properties: [
{
label: "Collides",
type: "bool",
inverse: true,
propertyID: "collisionless",
},
{
label: "Static Entities",
type: "bool",
propertyID: "collidesWithStatic",
propertyName: "static", // actual subProperty name
subPropertyOf: "collidesWith",
showPropertyRule: { "collisionless": "false" },
},
{
label: "Kinematic Entities",
type: "bool",
propertyID: "collidesWithKinematic",
propertyName: "kinematic", // actual subProperty name
subPropertyOf: "collidesWith",
showPropertyRule: { "collisionless": "false" },
},
{
label: "Dynamic Entities",
type: "bool",
propertyID: "collidesWithDynamic",
propertyName: "dynamic", // actual subProperty name
subPropertyOf: "collidesWith",
showPropertyRule: { "collisionless": "false" },
},
{
label: "My Avatar",
type: "bool",
propertyID: "collidesWithMyAvatar",
propertyName: "myAvatar", // actual subProperty name
subPropertyOf: "collidesWith",
showPropertyRule: { "collisionless": "false" },
},
{
label: "Other Avatars",
type: "bool",
propertyID: "collidesWithOtherAvatar",
propertyName: "otherAvatar", // actual subProperty name
subPropertyOf: "collidesWith",
showPropertyRule: { "collisionless": "false" },
},
{
label: "Collision Sound",
type: "string",
placeholder: "URL",
propertyID: "collisionSoundURL",
showPropertyRule: { "collisionless": "false" },
hideIfCertified: true,
},
{
label: "Dynamic",
type: "bool",
propertyID: "dynamic",
},
]
},
{
id: "physics",
label: "PHYSICS",
properties: [
{
label: "Linear Velocity",
type: "vec3",
vec3Type: "xyz",
step: 0.01,
decimals: 4,
subLabels: [ "x", "y", "z" ],
unit: "m/s",
propertyID: "localVelocity",
},
{
label: "Linear Damping",
type: "number-draggable",
min: 0,
max: 1,
step: 0.001,
decimals: 4,
propertyID: "damping",
},
{
label: "Angular Velocity",
type: "vec3",
vec3Type: "pyr",
multiplier: DEGREES_TO_RADIANS,
decimals: 4,
subLabels: [ "x", "y", "z" ],
unit: "deg/s",
propertyID: "localAngularVelocity",
},
{
label: "Angular Damping",
type: "number-draggable",
min: 0,
max: 1,
step: 0.001,
decimals: 4,
propertyID: "angularDamping",
},
{
label: "Bounciness",
type: "number-draggable",
step: 0.001,
decimals: 4,
propertyID: "restitution",
},
{
label: "Friction",
type: "number-draggable",
step: 0.01,
decimals: 4,
propertyID: "friction",
},
{
label: "Density",
type: "number-draggable",
step: 1,
decimals: 4,
propertyID: "density",
},
{
label: "Gravity",
type: "vec3",
vec3Type: "xyz",
subLabels: [ "x", "y", "z" ],
step: 0.1,
decimals: 4,
unit: "m/s<sup>2</sup>",
propertyID: "gravity",
},
{
label: "Acceleration",
type: "vec3",
vec3Type: "xyz",
subLabels: [ "x", "y", "z" ],
step: 0.1,
decimals: 4,
unit: "m/s<sup>2</sup>",
propertyID: "acceleration",
},
]
},
];
const GROUPS_PER_TYPE = {
None: [ 'base', 'spatial', 'behavior', 'collision', 'physics' ],
Shape: [ 'base', 'shape', 'spatial', 'behavior', 'collision', 'physics' ],
Text: [ 'base', 'text', 'spatial', 'behavior', 'collision', 'physics' ],
Zone: [ 'base', 'zone', 'spatial', 'behavior', 'physics' ],
Model: [ 'base', 'model', 'spatial', 'behavior', 'collision', 'physics' ],
Image: [ 'base', 'image', 'spatial', 'behavior', 'collision', 'physics' ],
Web: [ 'base', 'web', 'spatial', 'behavior', 'collision', 'physics' ],
Light: [ 'base', 'light', 'spatial', 'behavior', 'collision', 'physics' ],
Material: [ 'base', 'material', 'spatial', 'behavior' ],
ParticleEffect: [ 'base', 'particles', 'particles_emit', 'particles_size', 'particles_color', 'particles_alpha',
'particles_acceleration', 'particles_spin', 'particles_constraints', 'spatial', 'behavior', 'physics' ],
PolyLine: [ 'base', 'spatial', 'behavior', 'collision', 'physics' ],
PolyVox: [ 'base', 'spatial', 'behavior', 'collision', 'physics' ],
Grid: [ 'base', 'grid', 'spatial', 'behavior', 'physics' ],
Multiple: [ 'base', 'spatial', 'behavior', 'collision', 'physics' ],
};
const EDITOR_TIMEOUT_DURATION = 1500;
const DEBOUNCE_TIMEOUT = 125;
const COLOR_MIN = 0;
const COLOR_MAX = 255;
const COLOR_STEP = 1;
const MATERIAL_PREFIX_STRING = "mat::";
const PENDING_SCRIPT_STATUS = "[ Fetching status ]";
const NOT_RUNNING_SCRIPT_STATUS = "Not running";
const ENTITY_SCRIPT_STATUS = {
pending: "Pending",
loading: "Loading",
error_loading_script: "Error loading script", // eslint-disable-line camelcase
error_running_script: "Error running script", // eslint-disable-line camelcase
running: "Running",
unloaded: "Unloaded"
};
const ENABLE_DISABLE_SELECTOR = "input, textarea, span, .dropdown dl, .color-picker";
const PROPERTY_NAME_DIVISION = {
GROUP: 0,
PROPERTY: 1,
SUB_PROPERTY: 2,
};
const RECT_ELEMENTS = {
X_NUMBER: 0,
Y_NUMBER: 1,
WIDTH_NUMBER: 2,
HEIGHT_NUMBER: 3,
};
const VECTOR_ELEMENTS = {
X_NUMBER: 0,
Y_NUMBER: 1,
Z_NUMBER: 2,
};
const COLOR_ELEMENTS = {
COLOR_PICKER: 0,
RED_NUMBER: 1,
GREEN_NUMBER: 2,
BLUE_NUMBER: 3,
};
const TEXTURE_ELEMENTS = {
IMAGE: 0,
TEXT_INPUT: 1,
};
const JSON_EDITOR_ROW_DIV_INDEX = 2;
let elGroups = {};
let properties = {};
let propertyRangeRequests = [];
let colorPickers = {};
let particlePropertyUpdates = {};
let selectedEntityIDs = new Set();
let currentSelections = [];
let createAppTooltip = new CreateAppTooltip();
let currentSpaceMode = PROPERTY_SPACE_MODE.LOCAL;
function createElementFromHTML(htmlString) {
let elTemplate = document.createElement('template');
elTemplate.innerHTML = htmlString.trim();
return elTemplate.content.firstChild;
}
function isFlagSet(value, flag) {
return (value & flag) === flag;
}
/**
* GENERAL PROPERTY/GROUP FUNCTIONS
*/
function getPropertyInputElement(propertyID) {
let property = properties[propertyID];
switch (property.data.type) {
case 'string':
case 'number':
case 'bool':
case 'dropdown':
case 'textarea':
case 'texture':
return property.elInput;
case 'number-draggable':
return property.elNumber.elInput;
case 'rect':
return {
x: property.elNumberX.elInput,
y: property.elNumberY.elInput,
width: property.elNumberWidth.elInput,
height: property.elNumberHeight.elInput
};
case 'vec3':
case 'vec2':
return { x: property.elNumberX.elInput, y: property.elNumberY.elInput, z: property.elNumberZ.elInput };
case 'color':
return { red: property.elNumberR.elInput, green: property.elNumberG.elInput, blue: property.elNumberB.elInput };
case 'icon':
return property.elLabel;
case 'dynamic-multiselect':
return property.elDivOptions;
default:
return undefined;
}
}
function enableChildren(el, selector) {
let elSelectors = el.querySelectorAll(selector);
for (let selectorIndex = 0; selectorIndex < elSelectors.length; ++selectorIndex) {
elSelectors[selectorIndex].removeAttribute('disabled');
}
}
function disableChildren(el, selector) {
let elSelectors = el.querySelectorAll(selector);
for (let selectorIndex = 0; selectorIndex < elSelectors.length; ++selectorIndex) {
elSelectors[selectorIndex].setAttribute('disabled', 'disabled');
}
}
function enableProperties() {
enableChildren(document.getElementById("properties-list"), ENABLE_DISABLE_SELECTOR);
enableChildren(document, ".colpick");
let elLocked = getPropertyInputElement("locked");
if (elLocked.checked === false) {
removeStaticUserData();
removeStaticMaterialData();
}
}
function disableProperties() {
disableChildren(document.getElementById("properties-list"), ENABLE_DISABLE_SELECTOR);
disableChildren(document, ".colpick");
for (let pickKey in colorPickers) {
colorPickers[pickKey].colpickHide();
}
let elLocked = getPropertyInputElement("locked");
if (elLocked.checked === true) {
if ($('#property-userData-editor').css('display') === "block") {
showStaticUserData();
}
if ($('#property-materialData-editor').css('display') === "block") {
showStaticMaterialData();
}
}
}
function showPropertyElement(propertyID, show) {
setPropertyVisibility(properties[propertyID], show);
}
function setPropertyVisibility(property, visible) {
property.elContainer.style.display = visible ? null : "none";
}
function resetProperties() {
for (let propertyID in properties) {
let property = properties[propertyID];
let propertyData = property.data;
switch (propertyData.type) {
case 'number':
case 'string': {
property.elInput.classList.remove('multi-diff');
if (propertyData.defaultValue !== undefined) {
property.elInput.value = propertyData.defaultValue;
} else {
property.elInput.value = "";
}
break;
}
case 'bool': {
property.elInput.classList.remove('multi-diff');
property.elInput.checked = false;
break;
}
case 'number-draggable': {
if (propertyData.defaultValue !== undefined) {
property.elNumber.setValue(propertyData.defaultValue, false);
} else {
property.elNumber.setValue("", false);
}
break;
}
case 'rect': {
property.elNumberX.setValue("", false);
property.elNumberY.setValue("", false);
property.elNumberWidth.setValue("", false);
property.elNumberHeight.setValue("", false);
break;
}
case 'vec3':
case 'vec2': {
property.elNumberX.setValue("", false);
property.elNumberY.setValue("", false);
if (property.elNumberZ !== undefined) {
property.elNumberZ.setValue("", false);
}
break;
}
case 'color': {
property.elColorPicker.style.backgroundColor = "rgb(" + 0 + "," + 0 + "," + 0 + ")";
property.elNumberR.setValue("", false);
property.elNumberG.setValue("", false);
property.elNumberB.setValue("", false);
break;
}
case 'dropdown': {
property.elInput.classList.remove('multi-diff');
property.elInput.value = "";
setDropdownText(property.elInput);
break;
}
case 'textarea': {
property.elInput.classList.remove('multi-diff');
property.elInput.value = "";
setTextareaScrolling(property.elInput);
break;
}
case 'icon': {
property.elSpan.style.display = "none";
break;
}
case 'texture': {
property.elInput.classList.remove('multi-diff');
property.elInput.value = "";
property.elInput.imageLoad(property.elInput.value);
break;
}
case 'dynamic-multiselect': {
resetDynamicMultiselectProperty(property.elDivOptions);
break;
}
}
let showPropertyRules = properties[propertyID].showPropertyRules;
if (showPropertyRules !== undefined) {
for (let propertyToHide in showPropertyRules) {
showPropertyElement(propertyToHide, false);
}
}
}
resetServerScriptStatus();
}
function resetServerScriptStatus() {
let elServerScriptError = document.getElementById("property-serverScripts-error");
let elServerScriptStatus = document.getElementById("property-serverScripts-status");
elServerScriptError.parentElement.style.display = "none";
elServerScriptStatus.innerText = NOT_RUNNING_SCRIPT_STATUS;
}
function showGroupsForType(type) {
if (type === "Box" || type === "Sphere") {
showGroupsForTypes(["Shape"]);
return;
}
showGroupsForTypes([type]);
}
function getGroupsForTypes(types) {
return Object.keys(elGroups).filter((groupKey) => {
return types.map(type => GROUPS_PER_TYPE[type].includes(groupKey)).every(function (hasGroup) {
return hasGroup;
});
});
}
function showGroupsForTypes(types) {
Object.entries(elGroups).forEach(([groupKey, elGroup]) => {
if (types.map(type => GROUPS_PER_TYPE[type].includes(groupKey)).every(function (hasGroup) { return hasGroup; })) {
elGroup.style.display = "block";
} else {
elGroup.style.display = "none";
}
});
}
function getFirstSelectedID() {
if (selectedEntityIDs.size === 0) {
return null;
}
return selectedEntityIDs.values().next().value;
}
/**
* Returns true when the user is currently dragging the numeric slider control of the property
* @param propertyName - name of property
* @returns {boolean} currentlyDragging
*/
function isCurrentlyDraggingProperty(propertyName) {
return properties[propertyName] && properties[propertyName].dragging === true;
}
const SUPPORTED_FALLBACK_TYPES = ['number', 'number-draggable', 'rect', 'vec3', 'vec2', 'color'];
function getMultiplePropertyValue(originalPropertyName) {
// if this is a compound property name (i.e. animation.running)
// then split it by . up to 3 times to find property value
let propertyData = null;
if (properties[originalPropertyName] !== undefined) {
propertyData = properties[originalPropertyName].data;
}
let propertyValues = [];
let splitPropertyName = originalPropertyName.split('.');
if (splitPropertyName.length > 1) {
let propertyGroupName = splitPropertyName[PROPERTY_NAME_DIVISION.GROUP];
let propertyName = splitPropertyName[PROPERTY_NAME_DIVISION.PROPERTY];
propertyValues = currentSelections.map(selection => {
let groupProperties = selection.properties[propertyGroupName];
if (groupProperties === undefined || groupProperties[propertyName] === undefined) {
return undefined;
}
if (splitPropertyName.length === PROPERTY_NAME_DIVISION.SUB_PROPERTY + 1) {
let subPropertyName = splitPropertyName[PROPERTY_NAME_DIVISION.SUB_PROPERTY];
return groupProperties[propertyName][subPropertyName];
} else {
return groupProperties[propertyName];
}
});
} else {
propertyValues = currentSelections.map(selection => selection.properties[originalPropertyName]);
}
if (propertyData !== null && propertyData.fallbackProperty !== undefined &&
SUPPORTED_FALLBACK_TYPES.includes(propertyData.type)) {
let fallbackMultiValue = null;
for (let i = 0; i < propertyValues.length; ++i) {
let isPropertyNotNumber = false;
let propertyValue = propertyValues[i];
if (propertyValue === undefined) {
continue;
}
switch (propertyData.type) {
case 'number':
case 'number-draggable':
isPropertyNotNumber = isNaN(propertyValue) || propertyValue === null;
break;
case 'rect':
case 'vec3':
case 'vec2':
isPropertyNotNumber = isNaN(propertyValue.x) || propertyValue.x === null;
break;
case 'color':
isPropertyNotNumber = isNaN(propertyValue.red) || propertyValue.red === null;
break;
}
if (isPropertyNotNumber) {
if (fallbackMultiValue === null) {
fallbackMultiValue = getMultiplePropertyValue(propertyData.fallbackProperty);
}
propertyValues[i] = fallbackMultiValue.values[i];
}
}
}
const firstValue = propertyValues[0];
const isMultiDiffValue = !propertyValues.every((x) => deepEqual(firstValue, x));
if (isMultiDiffValue) {
return {
value: undefined,
values: propertyValues,
isMultiDiffValue: true
}
}
return {
value: propertyValues[0],
values: propertyValues,
isMultiDiffValue: false
};
}
/**
* Retrieve more detailed info for differing Numeric MultiplePropertyValue
* @param multiplePropertyValue - input multiplePropertyValue
* @param propertyData
* @returns {{keys: *[], propertyComponentDiff, averagePerPropertyComponent}}
*/
function getDetailedNumberMPVDiff(multiplePropertyValue, propertyData) {
let detailedValues = {};
// Fixed numbers can't be easily averaged since they're strings, so lets keep an array of unmodified numbers
let unmodifiedValues = {};
const DEFAULT_KEY = 0;
let uniqueKeys = new Set([]);
multiplePropertyValue.values.forEach(function(propertyValue) {
if (typeof propertyValue === "object") {
Object.entries(propertyValue).forEach(function([key, value]) {
if (!uniqueKeys.has(key)) {
uniqueKeys.add(key);
detailedValues[key] = [];
unmodifiedValues[key] = [];
}
detailedValues[key].push(applyInputNumberPropertyModifiers(value, propertyData));
unmodifiedValues[key].push(value);
});
} else {
if (!uniqueKeys.has(DEFAULT_KEY)) {
uniqueKeys.add(DEFAULT_KEY);
detailedValues[DEFAULT_KEY] = [];
unmodifiedValues[DEFAULT_KEY] = [];
}
detailedValues[DEFAULT_KEY].push(applyInputNumberPropertyModifiers(propertyValue, propertyData));
unmodifiedValues[DEFAULT_KEY].push(propertyValue);
}
});
let keys = [...uniqueKeys];
let propertyComponentDiff = {};
Object.entries(detailedValues).forEach(function([key, value]) {
propertyComponentDiff[key] = [...new Set(value)].length > 1;
});
let averagePerPropertyComponent = {};
Object.entries(unmodifiedValues).forEach(function([key, value]) {
let average = value.reduce((a, b) => a + b) / value.length;
averagePerPropertyComponent[key] = applyInputNumberPropertyModifiers(average, propertyData);
});
return {
keys,
propertyComponentDiff,
averagePerPropertyComponent,
};
}
function getDetailedSubPropertyMPVDiff(multiplePropertyValue, subPropertyName) {
let isChecked = false;
let checkedValues = multiplePropertyValue.values.map((value) => value.split(",").includes(subPropertyName));
let isMultiDiff = !checkedValues.every(value => value === checkedValues[0]);
if (!isMultiDiff) {
isChecked = checkedValues[0];
}
return {
isChecked,
isMultiDiff
}
}
function updateVisibleSpaceModeProperties() {
for (let propertyID in properties) {
if (properties.hasOwnProperty(propertyID)) {
let property = properties[propertyID];
let propertySpaceMode = property.spaceMode;
let elProperty = properties[propertyID].elContainer;
if (propertySpaceMode !== PROPERTY_SPACE_MODE.ALL && propertySpaceMode !== currentSpaceMode) {
elProperty.classList.add('spacemode-hidden');
} else {
elProperty.classList.remove('spacemode-hidden');
}
}
}
}
/**
* PROPERTY UPDATE FUNCTIONS
*/
function createPropertyUpdateObject(originalPropertyName, propertyValue) {
let propertyUpdate = {};
// if this is a compound property name (i.e. animation.running) then split it by . up to 3 times
let splitPropertyName = originalPropertyName.split('.');
if (splitPropertyName.length > 1) {
let propertyGroupName = splitPropertyName[PROPERTY_NAME_DIVISION.GROUP];
let propertyName = splitPropertyName[PROPERTY_NAME_DIVISION.PROPERTY];
propertyUpdate[propertyGroupName] = {};
if (splitPropertyName.length === PROPERTY_NAME_DIVISION.SUB_PROPERTY + 1) {
let subPropertyName = splitPropertyName[PROPERTY_NAME_DIVISION.SUB_PROPERTY];
propertyUpdate[propertyGroupName][propertyName] = {};
propertyUpdate[propertyGroupName][propertyName][subPropertyName] = propertyValue;
} else {
propertyUpdate[propertyGroupName][propertyName] = propertyValue;
}
} else {
propertyUpdate[originalPropertyName] = propertyValue;
}
return propertyUpdate;
}
function updateProperty(originalPropertyName, propertyValue, isParticleProperty) {
let propertyUpdate = createPropertyUpdateObject(originalPropertyName, propertyValue);
// queue up particle property changes with the debounced sync to avoid
// causing particle emitting to reset excessively with each value change
if (isParticleProperty) {
Object.keys(propertyUpdate).forEach(function (propertyUpdateKey) {
particlePropertyUpdates[propertyUpdateKey] = propertyUpdate[propertyUpdateKey];
});
particleSyncDebounce();
} else {
// only update the entity property value itself if in the middle of dragging
// prevent undo command push, saving new property values, and property update
// callback until drag is complete (additional update sent via dragEnd callback)
let onlyUpdateEntity = isCurrentlyDraggingProperty(originalPropertyName);
updateProperties(propertyUpdate, onlyUpdateEntity);
}
}
let particleSyncDebounce = _.debounce(function () {
updateProperties(particlePropertyUpdates);
particlePropertyUpdates = {};
}, DEBOUNCE_TIMEOUT);
function updateProperties(propertiesToUpdate, onlyUpdateEntity) {
if (onlyUpdateEntity === undefined) {
onlyUpdateEntity = false;
}
EventBridge.emitWebEvent(JSON.stringify({
ids: [...selectedEntityIDs],
type: "update",
properties: propertiesToUpdate,
onlyUpdateEntities: onlyUpdateEntity
}));
}
function updateMultiDiffProperties(propertiesMapToUpdate, onlyUpdateEntity) {
if (onlyUpdateEntity === undefined) {
onlyUpdateEntity = false;
}
EventBridge.emitWebEvent(JSON.stringify({
type: "update",
propertiesMap: propertiesMapToUpdate,
onlyUpdateEntities: onlyUpdateEntity
}));
}
function createEmitTextPropertyUpdateFunction(property) {
return function() {
property.elInput.classList.remove('multi-diff');
updateProperty(property.name, this.value, property.isParticleProperty);
};
}
function createEmitCheckedPropertyUpdateFunction(property) {
return function() {
updateProperty(property.name, property.data.inverse ? !this.checked : this.checked, property.isParticleProperty);
};
}
function createDragStartFunction(property) {
return function() {
property.dragging = true;
};
}
function createDragEndFunction(property) {
return function() {
property.dragging = false;
if (this.multiDiffModeEnabled) {
let propertyMultiValue = getMultiplePropertyValue(property.name);
let updateObjects = [];
const selectedEntityIDsArray = [...selectedEntityIDs];
for (let i = 0; i < selectedEntityIDsArray.length; ++i) {
let entityID = selectedEntityIDsArray[i];
updateObjects.push({
entityIDs: [entityID],
properties: createPropertyUpdateObject(property.name, propertyMultiValue.values[i]),
});
}
// send a full updateMultiDiff post-dragging to count as an action in the undo stack
updateMultiDiffProperties(updateObjects);
} else {
// send an additional update post-dragging to consider whole property change from dragStart to dragEnd to be 1 action
this.valueChangeFunction();
}
};
}
function createEmitNumberPropertyUpdateFunction(property) {
return function() {
let value = parseFloat(applyOutputNumberPropertyModifiers(parseFloat(this.value), property.data));
updateProperty(property.name, value, property.isParticleProperty);
};
}
function createEmitNumberPropertyComponentUpdateFunction(property, propertyComponent) {
return function() {
let propertyMultiValue = getMultiplePropertyValue(property.name);
let value = parseFloat(applyOutputNumberPropertyModifiers(parseFloat(this.value), property.data));
if (propertyMultiValue.isMultiDiffValue) {
let updateObjects = [];
const selectedEntityIDsArray = [...selectedEntityIDs];
for (let i = 0; i < selectedEntityIDsArray.length; ++i) {
let entityID = selectedEntityIDsArray[i];
let propertyObject = propertyMultiValue.values[i];
propertyObject[propertyComponent] = value;
let updateObject = createPropertyUpdateObject(property.name, propertyObject);
updateObjects.push({
entityIDs: [entityID],
properties: updateObject,
});
mergeDeep(currentSelections[i].properties, updateObject);
}
// only update the entity property value itself if in the middle of dragging
// prevent undo command push, saving new property values, and property update
// callback until drag is complete (additional update sent via dragEnd callback)
let onlyUpdateEntity = isCurrentlyDraggingProperty(property.name);
updateMultiDiffProperties(updateObjects, onlyUpdateEntity);
} else {
let propertyValue = propertyMultiValue.value;
propertyValue[propertyComponent] = value;
updateProperty(property.name, propertyValue, property.isParticleProperty);
}
};
}
function createEmitColorPropertyUpdateFunction(property) {
return function() {
emitColorPropertyUpdate(property.name, property.elNumberR.elInput.value, property.elNumberG.elInput.value,
property.elNumberB.elInput.value, property.isParticleProperty);
};
}
function emitColorPropertyUpdate(propertyName, red, green, blue, isParticleProperty) {
let newValue = {
red: red,
green: green,
blue: blue
};
updateProperty(propertyName, newValue, isParticleProperty);
}
function toggleBooleanCSV(inputCSV, property, enable) {
let values = inputCSV.split(",");
if (enable && !values.includes(property)) {
values.push(property);
} else if (!enable && values.includes(property)) {
values = values.filter(value => value !== property);
}
return values.join(",");
}
function updateCheckedSubProperty(propertyName, propertyMultiValue, subPropertyElement, subPropertyString, isParticleProperty) {
if (propertyMultiValue.isMultiDiffValue) {
let updateObjects = [];
const selectedEntityIDsArray = [...selectedEntityIDs];
for (let i = 0; i < selectedEntityIDsArray.length; ++i) {
let newValue = toggleBooleanCSV(propertyMultiValue.values[i], subPropertyString, subPropertyElement.checked);
updateObjects.push({
entityIDs: [selectedEntityIDsArray[i]],
properties: createPropertyUpdateObject(propertyName, newValue),
});
}
updateMultiDiffProperties(updateObjects);
} else {
updateProperty(propertyName, toggleBooleanCSV(propertyMultiValue.value, subPropertyString, subPropertyElement.checked),
isParticleProperty);
}
}
/**
* PROPERTY ELEMENT CREATION FUNCTIONS
*/
function createStringProperty(property, elProperty) {
let elementID = property.elementID;
let propertyData = property.data;
elProperty.className = "text";
let elInput = createElementFromHTML(`
<input id="${elementID}"
type="text"
${propertyData.placeholder ? 'placeholder="' + propertyData.placeholder + '"' : ''}
${propertyData.readOnly ? 'readonly' : ''}/>
`);
elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(property));
if (propertyData.onChange !== undefined) {
elInput.addEventListener('change', propertyData.onChange);
}
let elMultiDiff = document.createElement('span');
elMultiDiff.className = "multi-diff";
elProperty.appendChild(elInput);
elProperty.appendChild(elMultiDiff);
if (propertyData.buttons !== undefined) {
addButtons(elProperty, elementID, propertyData.buttons, false);
}
return elInput;
}
function createBoolProperty(property, elProperty) {
let propertyName = property.name;
let elementID = property.elementID;
let propertyData = property.data;
elProperty.className = "checkbox";
if (propertyData.glyph !== undefined) {
let elSpan = document.createElement('span');
elSpan.innerHTML = propertyData.glyph;
elSpan.className = 'icon';
elProperty.appendChild(elSpan);
}
let elInput = document.createElement('input');
elInput.setAttribute("id", elementID);
elInput.setAttribute("type", "checkbox");
elProperty.appendChild(elInput);
elProperty.appendChild(createElementFromHTML(`<label for=${elementID}>&nbsp;</label>`));
let subPropertyOf = propertyData.subPropertyOf;
if (subPropertyOf !== undefined) {
elInput.addEventListener('change', function() {
let subPropertyMultiValue = getMultiplePropertyValue(subPropertyOf);
updateCheckedSubProperty(subPropertyOf,
subPropertyMultiValue,
elInput, propertyName, property.isParticleProperty);
});
} else {
elInput.addEventListener('change', createEmitCheckedPropertyUpdateFunction(property));
}
return elInput;
}
function createNumberProperty(property, elProperty) {
let elementID = property.elementID;
let propertyData = property.data;
elProperty.className = "text";
let elInput = createElementFromHTML(`
<input id="${elementID}"
class='hide-spinner'
type="number"
${propertyData.placeholder ? 'placeholder="' + propertyData.placeholder + '"' : ''}
${propertyData.readOnly ? 'readonly' : ''}/>
`);
if (propertyData.min !== undefined) {
elInput.setAttribute("min", propertyData.min);
}
if (propertyData.max !== undefined) {
elInput.setAttribute("max", propertyData.max);
}
if (propertyData.step !== undefined) {
elInput.setAttribute("step", propertyData.step);
}
if (propertyData.defaultValue !== undefined) {
elInput.value = propertyData.defaultValue;
}
elInput.addEventListener('change', createEmitNumberPropertyUpdateFunction(property));
let elMultiDiff = document.createElement('span');
elMultiDiff.className = "multi-diff";
elProperty.appendChild(elInput);
elProperty.appendChild(elMultiDiff);
if (propertyData.buttons !== undefined) {
addButtons(elProperty, elementID, propertyData.buttons, false);
}
return elInput;
}
function updateNumberMinMax(property) {
let elInput = property.elInput;
let min = property.data.min;
let max = property.data.max;
if (min !== undefined) {
elInput.setAttribute("min", min);
}
if (max !== undefined) {
elInput.setAttribute("max", max);
}
}
/**
*
* @param {object} property - property update on step
* @param {string} [propertyComponent] - propertyComponent to update on step (e.g. enter 'x' to just update position.x)
* @returns {Function}
*/
function createMultiDiffStepFunction(property, propertyComponent) {
return function(step, shouldAddToUndoHistory) {
if (shouldAddToUndoHistory === undefined) {
shouldAddToUndoHistory = false;
}
let propertyMultiValue = getMultiplePropertyValue(property.name);
if (!propertyMultiValue.isMultiDiffValue) {
console.log("setMultiDiffStepFunction is only supposed to be called in MultiDiff mode.");
return;
}
let multiplier = property.data.multiplier !== undefined ? property.data.multiplier : 1;
let applyDelta = step * multiplier;
if (selectedEntityIDs.size !== propertyMultiValue.values.length) {
console.log("selectedEntityIDs and propertyMultiValue got out of sync.");
return;
}
let updateObjects = [];
const selectedEntityIDsArray = [...selectedEntityIDs];
for (let i = 0; i < selectedEntityIDsArray.length; ++i) {
let entityID = selectedEntityIDsArray[i];
let updatedValue;
if (propertyComponent !== undefined) {
let objectToUpdate = propertyMultiValue.values[i];
objectToUpdate[propertyComponent] += applyDelta;
updatedValue = objectToUpdate;
} else {
updatedValue = propertyMultiValue.values[i] + applyDelta;
}
let propertiesUpdate = createPropertyUpdateObject(property.name, updatedValue);
updateObjects.push({
entityIDs: [entityID],
properties: propertiesUpdate
});
// We need to store these so that we can send a full update on the dragEnd
mergeDeep(currentSelections[i].properties, propertiesUpdate);
}
updateMultiDiffProperties(updateObjects, !shouldAddToUndoHistory);
}
}
function createNumberDraggableProperty(property, elProperty) {
let elementID = property.elementID;
let propertyData = property.data;
elProperty.className += " draggable-number-container";
let dragStartFunction = createDragStartFunction(property);
let dragEndFunction = createDragEndFunction(property);
let elDraggableNumber = new DraggableNumber(propertyData.min, propertyData.max, propertyData.step,
propertyData.decimals, dragStartFunction, dragEndFunction);
let defaultValue = propertyData.defaultValue;
if (defaultValue !== undefined) {
elDraggableNumber.elInput.value = defaultValue;
}
let valueChangeFunction = createEmitNumberPropertyUpdateFunction(property);
elDraggableNumber.setValueChangeFunction(valueChangeFunction);
elDraggableNumber.setMultiDiffStepFunction(createMultiDiffStepFunction(property));
elDraggableNumber.elInput.setAttribute("id", elementID);
elProperty.appendChild(elDraggableNumber.elDiv);
if (propertyData.buttons !== undefined) {
addButtons(elDraggableNumber.elDiv, elementID, propertyData.buttons, false);
}
return elDraggableNumber;
}
function updateNumberDraggableMinMax(property) {
let propertyData = property.data;
property.elNumber.updateMinMax(propertyData.min, propertyData.max);
}
function createRectProperty(property, elProperty) {
let propertyData = property.data;
elProperty.className = "rect";
let elXYRow = document.createElement('div');
elXYRow.className = "rect-row fstuple";
elProperty.appendChild(elXYRow);
let elWidthHeightRow = document.createElement('div');
elWidthHeightRow.className = "rect-row fstuple";
elProperty.appendChild(elWidthHeightRow);
let elNumberX = createTupleNumberInput(property, propertyData.subLabels[RECT_ELEMENTS.X_NUMBER]);
let elNumberY = createTupleNumberInput(property, propertyData.subLabels[RECT_ELEMENTS.Y_NUMBER]);
let elNumberWidth = createTupleNumberInput(property, propertyData.subLabels[RECT_ELEMENTS.WIDTH_NUMBER]);
let elNumberHeight = createTupleNumberInput(property, propertyData.subLabels[RECT_ELEMENTS.HEIGHT_NUMBER]);
elXYRow.appendChild(elNumberX.elDiv);
elXYRow.appendChild(elNumberY.elDiv);
elWidthHeightRow.appendChild(elNumberWidth.elDiv);
elWidthHeightRow.appendChild(elNumberHeight.elDiv);
elNumberX.setValueChangeFunction(createEmitNumberPropertyComponentUpdateFunction(property, 'x'));
elNumberY.setValueChangeFunction(createEmitNumberPropertyComponentUpdateFunction(property, 'y'));
elNumberWidth.setValueChangeFunction(createEmitNumberPropertyComponentUpdateFunction(property, 'width'));
elNumberHeight.setValueChangeFunction(createEmitNumberPropertyComponentUpdateFunction(property, 'height'));
elNumberX.setMultiDiffStepFunction(createMultiDiffStepFunction(property, 'x'));
elNumberY.setMultiDiffStepFunction(createMultiDiffStepFunction(property, 'y'));
elNumberX.setMultiDiffStepFunction(createMultiDiffStepFunction(property, 'width'));
elNumberY.setMultiDiffStepFunction(createMultiDiffStepFunction(property, 'height'));
let elResult = [];
elResult[RECT_ELEMENTS.X_NUMBER] = elNumberX;
elResult[RECT_ELEMENTS.Y_NUMBER] = elNumberY;
elResult[RECT_ELEMENTS.WIDTH_NUMBER] = elNumberWidth;
elResult[RECT_ELEMENTS.HEIGHT_NUMBER] = elNumberHeight;
return elResult;
}
function updateRectMinMax(property) {
let min = property.data.min;
let max = property.data.max;
property.elNumberX.updateMinMax(min, max);
property.elNumberY.updateMinMax(min, max);
property.elNumberWidth.updateMinMax(min, max);
property.elNumberHeight.updateMinMax(min, max);
}
function createVec3Property(property, elProperty) {
let propertyData = property.data;
elProperty.className = propertyData.vec3Type + " fstuple";
let elNumberX = createTupleNumberInput(property, propertyData.subLabels[VECTOR_ELEMENTS.X_NUMBER]);
let elNumberY = createTupleNumberInput(property, propertyData.subLabels[VECTOR_ELEMENTS.Y_NUMBER]);
let elNumberZ = createTupleNumberInput(property, propertyData.subLabels[VECTOR_ELEMENTS.Z_NUMBER]);
elProperty.appendChild(elNumberX.elDiv);
elProperty.appendChild(elNumberY.elDiv);
elProperty.appendChild(elNumberZ.elDiv);
elNumberX.setValueChangeFunction(createEmitNumberPropertyComponentUpdateFunction(property, 'x'));
elNumberY.setValueChangeFunction(createEmitNumberPropertyComponentUpdateFunction(property, 'y'));
elNumberZ.setValueChangeFunction(createEmitNumberPropertyComponentUpdateFunction(property, 'z'));
elNumberX.setMultiDiffStepFunction(createMultiDiffStepFunction(property, 'x'));
elNumberY.setMultiDiffStepFunction(createMultiDiffStepFunction(property, 'y'));
elNumberZ.setMultiDiffStepFunction(createMultiDiffStepFunction(property, 'z'));
let elResult = [];
elResult[VECTOR_ELEMENTS.X_NUMBER] = elNumberX;
elResult[VECTOR_ELEMENTS.Y_NUMBER] = elNumberY;
elResult[VECTOR_ELEMENTS.Z_NUMBER] = elNumberZ;
return elResult;
}
function createVec2Property(property, elProperty) {
let propertyData = property.data;
elProperty.className = propertyData.vec2Type + " fstuple";
let elTuple = document.createElement('div');
elTuple.className = "tuple";
elProperty.appendChild(elTuple);
let elNumberX = createTupleNumberInput(property, propertyData.subLabels[VECTOR_ELEMENTS.X_NUMBER]);
let elNumberY = createTupleNumberInput(property, propertyData.subLabels[VECTOR_ELEMENTS.Y_NUMBER]);
elProperty.appendChild(elNumberX.elDiv);
elProperty.appendChild(elNumberY.elDiv);
elNumberX.setValueChangeFunction(createEmitNumberPropertyComponentUpdateFunction(property, 'x'));
elNumberY.setValueChangeFunction(createEmitNumberPropertyComponentUpdateFunction(property, 'y'));
elNumberX.setMultiDiffStepFunction(createMultiDiffStepFunction(property, 'x'));
elNumberY.setMultiDiffStepFunction(createMultiDiffStepFunction(property, 'y'));
let elResult = [];
elResult[VECTOR_ELEMENTS.X_NUMBER] = elNumberX;
elResult[VECTOR_ELEMENTS.Y_NUMBER] = elNumberY;
return elResult;
}
function updateVectorMinMax(property) {
let min = property.data.min;
let max = property.data.max;
property.elNumberX.updateMinMax(min, max);
property.elNumberY.updateMinMax(min, max);
if (property.elNumberZ) {
property.elNumberZ.updateMinMax(min, max);
}
}
function createColorProperty(property, elProperty) {
let propertyName = property.name;
let elementID = property.elementID;
let propertyData = property.data;
elProperty.className += " rgb fstuple";
let elColorPicker = document.createElement('div');
elColorPicker.className = "color-picker";
elColorPicker.setAttribute("id", elementID);
let elTuple = document.createElement('div');
elTuple.className = "tuple";
elProperty.appendChild(elColorPicker);
elProperty.appendChild(elTuple);
if (propertyData.min === undefined) {
propertyData.min = COLOR_MIN;
}
if (propertyData.max === undefined) {
propertyData.max = COLOR_MAX;
}
if (propertyData.step === undefined) {
propertyData.step = COLOR_STEP;
}
let elNumberR = createTupleNumberInput(property, "red");
let elNumberG = createTupleNumberInput(property, "green");
let elNumberB = createTupleNumberInput(property, "blue");
elTuple.appendChild(elNumberR.elDiv);
elTuple.appendChild(elNumberG.elDiv);
elTuple.appendChild(elNumberB.elDiv);
let valueChangeFunction = createEmitColorPropertyUpdateFunction(property);
elNumberR.setValueChangeFunction(valueChangeFunction);
elNumberG.setValueChangeFunction(valueChangeFunction);
elNumberB.setValueChangeFunction(valueChangeFunction);
let colorPickerID = "#" + elementID;
colorPickers[colorPickerID] = $(colorPickerID).colpick({
colorScheme: 'dark',
layout: 'rgbhex',
color: '000000',
submit: false, // We don't want to have a submission button
onShow: function(colpick) {
// The original color preview within the picker needs to be updated on show because
// prior to the picker being shown we don't have access to the selections' starting color.
colorPickers[colorPickerID].colpickSetColor({
"r": elNumberR.elInput.value,
"g": elNumberG.elInput.value,
"b": elNumberB.elInput.value
});
// Set the color picker active after setting the color, otherwise an update will be sent on open.
$(colorPickerID).attr('active', 'true');
},
onHide: function(colpick) {
$(colorPickerID).attr('active', 'false');
},
onChange: function(hsb, hex, rgb, el) {
$(el).css('background-color', '#' + hex);
if ($(colorPickerID).attr('active') === 'true') {
emitColorPropertyUpdate(propertyName, rgb.r, rgb.g, rgb.b);
}
}
});
let elResult = [];
elResult[COLOR_ELEMENTS.COLOR_PICKER] = elColorPicker;
elResult[COLOR_ELEMENTS.RED_NUMBER] = elNumberR;
elResult[COLOR_ELEMENTS.GREEN_NUMBER] = elNumberG;
elResult[COLOR_ELEMENTS.BLUE_NUMBER] = elNumberB;
return elResult;
}
function createDropdownProperty(property, propertyID, elProperty) {
let elementID = property.elementID;
let propertyData = property.data;
elProperty.className = "dropdown";
let elInput = document.createElement('select');
elInput.setAttribute("id", elementID);
elInput.setAttribute("propertyID", propertyID);
for (let optionKey in propertyData.options) {
let option = document.createElement('option');
option.value = optionKey;
option.text = propertyData.options[optionKey];
elInput.add(option);
}
elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(property));
elProperty.appendChild(elInput);
return elInput;
}
function createTextareaProperty(property, elProperty) {
let elementID = property.elementID;
let propertyData = property.data;
elProperty.className = "textarea";
let elInput = document.createElement('textarea');
elInput.setAttribute("id", elementID);
if (propertyData.readOnly) {
elInput.readOnly = true;
}
elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(property));
let elMultiDiff = document.createElement('span');
elMultiDiff.className = "multi-diff";
elProperty.appendChild(elInput);
elProperty.appendChild(elMultiDiff);
if (propertyData.buttons !== undefined) {
addButtons(elProperty, elementID, propertyData.buttons, true);
}
return elInput;
}
function createIconProperty(property, elProperty) {
let elementID = property.elementID;
elProperty.className = "value";
let elSpan = document.createElement('span');
elSpan.setAttribute("id", elementID + "-icon");
elSpan.className = 'icon';
elProperty.appendChild(elSpan);
return elSpan;
}
function createTextureProperty(property, elProperty) {
let elementID = property.elementID;
elProperty.className = "texture";
let elDiv = document.createElement("div");
let elImage = document.createElement("img");
elDiv.className = "texture-image no-texture";
elDiv.appendChild(elImage);
let elInput = document.createElement('input');
elInput.setAttribute("id", elementID);
elInput.setAttribute("type", "text");
let imageLoad = function(url) {
elDiv.style.display = null;
if (url.slice(0, 5).toLowerCase() === "atp:/") {
elImage.src = "";
elImage.style.display = "none";
elDiv.classList.remove("with-texture");
elDiv.classList.remove("no-texture");
elDiv.classList.add("no-preview");
} else if (url.length > 0) {
elDiv.classList.remove("no-texture");
elDiv.classList.remove("no-preview");
elDiv.classList.add("with-texture");
elImage.src = url;
elImage.style.display = "block";
} else {
elImage.src = "";
elImage.style.display = "none";
elDiv.classList.remove("with-texture");
elDiv.classList.remove("no-preview");
elDiv.classList.add("no-texture");
}
};
elInput.imageLoad = imageLoad;
elInput.setMultipleValues = function() {
elDiv.style.display = "none";
};
elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(property));
elInput.addEventListener('change', function(ev) {
imageLoad(ev.target.value);
});
elProperty.appendChild(elInput);
let elMultiDiff = document.createElement('span');
elMultiDiff.className = "multi-diff";
elProperty.appendChild(elMultiDiff);
elProperty.appendChild(elDiv);
let elResult = [];
elResult[TEXTURE_ELEMENTS.IMAGE] = elImage;
elResult[TEXTURE_ELEMENTS.TEXT_INPUT] = elInput;
return elResult;
}
function createButtonsProperty(property, elProperty) {
let elementID = property.elementID;
let propertyData = property.data;
elProperty.className = "text";
if (propertyData.buttons !== undefined) {
addButtons(elProperty, elementID, propertyData.buttons, false);
}
return elProperty;
}
function createDynamicMultiselectProperty(property, elProperty) {
let elementID = property.elementID;
let propertyData = property.data;
elProperty.className = "dynamic-multiselect";
let elDivOptions = document.createElement('div');
elDivOptions.setAttribute("id", elementID + "-options");
elDivOptions.style = "overflow-y:scroll;max-height:160px;";
let elDivButtons = document.createElement('div');
elDivButtons.setAttribute("id", elDivOptions.getAttribute("id") + "-buttons");
let elLabel = document.createElement('label');
elLabel.innerText = "No Options";
elDivOptions.appendChild(elLabel);
let buttons = [ { id: "selectAll", label: "Select All", className: "black", onClick: selectAllMaterialTarget },
{ id: "clearAll", label: "Clear All", className: "black", onClick: clearAllMaterialTarget } ];
addButtons(elDivButtons, elementID, buttons, false);
elProperty.appendChild(elDivOptions);
elProperty.appendChild(elDivButtons);
return elDivOptions;
}
function resetDynamicMultiselectProperty(elDivOptions) {
let elInputs = elDivOptions.getElementsByTagName("input");
while (elInputs.length > 0) {
let elDivOption = elInputs[0].parentNode;
elDivOption.parentNode.removeChild(elDivOption);
}
elDivOptions.firstChild.style.display = null; // show "No Options" text
elDivOptions.parentNode.lastChild.style.display = "none"; // hide Select/Clear all buttons
}
function createTupleNumberInput(property, subLabel) {
let propertyElementID = property.elementID;
let propertyData = property.data;
let elementID = propertyElementID + "-" + subLabel.toLowerCase();
let elLabel = document.createElement('label');
elLabel.className = "sublabel " + subLabel;
elLabel.innerText = subLabel[0].toUpperCase() + subLabel.slice(1);
elLabel.setAttribute("for", elementID);
elLabel.style.visibility = "visible";
let dragStartFunction = createDragStartFunction(property);
let dragEndFunction = createDragEndFunction(property);
let elDraggableNumber = new DraggableNumber(propertyData.min, propertyData.max, propertyData.step,
propertyData.decimals, dragStartFunction, dragEndFunction);
elDraggableNumber.elInput.setAttribute("id", elementID);
elDraggableNumber.elDiv.className += " fstuple";
elDraggableNumber.elDiv.insertBefore(elLabel, elDraggableNumber.elLeftArrow);
return elDraggableNumber;
}
function addButtons(elProperty, propertyID, buttons, newRow) {
let elDiv = document.createElement('div');
elDiv.className = "row";
buttons.forEach(function(button) {
let elButton = document.createElement('input');
elButton.className = button.className;
elButton.setAttribute("type", "button");
elButton.setAttribute("id", propertyID + "-button-" + button.id);
elButton.setAttribute("value", button.label);
elButton.addEventListener("click", button.onClick);
if (newRow) {
elDiv.appendChild(elButton);
} else {
elProperty.appendChild(elButton);
}
});
if (newRow) {
elProperty.appendChild(document.createElement('br'));
elProperty.appendChild(elDiv);
}
}
function createProperty(propertyData, propertyElementID, propertyName, propertyID, elProperty) {
let property = {
data: propertyData,
elementID: propertyElementID,
name: propertyName,
elProperty: elProperty,
};
let propertyType = propertyData.type;
switch (propertyType) {
case 'string': {
property.elInput = createStringProperty(property, elProperty);
break;
}
case 'bool': {
property.elInput = createBoolProperty(property, elProperty);
break;
}
case 'number': {
property.elInput = createNumberProperty(property, elProperty);
break;
}
case 'number-draggable': {
property.elNumber = createNumberDraggableProperty(property, elProperty);
break;
}
case 'rect': {
let elRect = createRectProperty(property, elProperty);
property.elNumberX = elRect[RECT_ELEMENTS.X_NUMBER];
property.elNumberY = elRect[RECT_ELEMENTS.Y_NUMBER];
property.elNumberWidth = elRect[RECT_ELEMENTS.WIDTH_NUMBER];
property.elNumberHeight = elRect[RECT_ELEMENTS.HEIGHT_NUMBER];
break;
}
case 'vec3': {
let elVec3 = createVec3Property(property, elProperty);
property.elNumberX = elVec3[VECTOR_ELEMENTS.X_NUMBER];
property.elNumberY = elVec3[VECTOR_ELEMENTS.Y_NUMBER];
property.elNumberZ = elVec3[VECTOR_ELEMENTS.Z_NUMBER];
break;
}
case 'vec2': {
let elVec2 = createVec2Property(property, elProperty);
property.elNumberX = elVec2[VECTOR_ELEMENTS.X_NUMBER];
property.elNumberY = elVec2[VECTOR_ELEMENTS.Y_NUMBER];
break;
}
case 'color': {
let elColor = createColorProperty(property, elProperty);
property.elColorPicker = elColor[COLOR_ELEMENTS.COLOR_PICKER];
property.elNumberR = elColor[COLOR_ELEMENTS.RED_NUMBER];
property.elNumberG = elColor[COLOR_ELEMENTS.GREEN_NUMBER];
property.elNumberB = elColor[COLOR_ELEMENTS.BLUE_NUMBER];
break;
}
case 'dropdown': {
property.elInput = createDropdownProperty(property, propertyID, elProperty);
break;
}
case 'textarea': {
property.elInput = createTextareaProperty(property, elProperty);
break;
}
case 'icon': {
property.elSpan = createIconProperty(property, elProperty);
break;
}
case 'texture': {
let elTexture = createTextureProperty(property, elProperty);
property.elImage = elTexture[TEXTURE_ELEMENTS.IMAGE];
property.elInput = elTexture[TEXTURE_ELEMENTS.TEXT_INPUT];
break;
}
case 'buttons': {
property.elProperty = createButtonsProperty(property, elProperty);
break;
}
case 'dynamic-multiselect': {
property.elDivOptions = createDynamicMultiselectProperty(property, elProperty);
break;
}
case 'placeholder':
case 'sub-header': {
break;
}
default: {
console.log("EntityProperties - Unknown property type " +
propertyType + " set to property " + propertyID);
break;
}
}
return property;
}
/**
* PROPERTY-SPECIFIC CALLBACKS
*/
function parentIDChanged() {
if (currentSelections.length === 1 && currentSelections[0].properties.type === "Material") {
requestMaterialTarget();
}
}
/**
* BUTTON CALLBACKS
*/
function rescaleDimensions() {
EventBridge.emitWebEvent(JSON.stringify({
type: "action",
action: "rescaleDimensions",
percentage: parseFloat(document.getElementById("property-scale").value)
}));
}
function moveSelectionToGrid() {
EventBridge.emitWebEvent(JSON.stringify({
type: "action",
action: "moveSelectionToGrid"
}));
}
function moveAllToGrid() {
EventBridge.emitWebEvent(JSON.stringify({
type: "action",
action: "moveAllToGrid"
}));
}
function resetToNaturalDimensions() {
EventBridge.emitWebEvent(JSON.stringify({
type: "action",
action: "resetToNaturalDimensions"
}));
}
function reloadScripts() {
EventBridge.emitWebEvent(JSON.stringify({
type: "action",
action: "reloadClientScripts"
}));
}
function reloadServerScripts() {
// invalidate the current status (so that same-same updates can still be observed visually)
document.getElementById("property-serverScripts-status").innerText = PENDING_SCRIPT_STATUS;
EventBridge.emitWebEvent(JSON.stringify({
type: "action",
action: "reloadServerScripts"
}));
}
function copySkyboxURLToAmbientURL() {
let skyboxURL = getPropertyInputElement("skybox.url").value;
getPropertyInputElement("ambientLight.ambientURL").value = skyboxURL;
updateProperty("ambientLight.ambientURL", skyboxURL, false);
}
/**
* USER DATA FUNCTIONS
*/
function clearUserData() {
let elUserData = getPropertyInputElement("userData");
deleteJSONEditor();
elUserData.value = "";
showUserDataTextArea();
showNewJSONEditorButton();
hideSaveUserDataButton();
updateProperty('userData', elUserData.value, false);
}
function newJSONEditor() {
getPropertyInputElement("userData").classList.remove('multi-diff');
deleteJSONEditor();
createJSONEditor();
let data = {};
setEditorJSON(data);
hideUserDataTextArea();
hideNewJSONEditorButton();
showSaveUserDataButton();
}
/**
* @param {Set.<string>} [entityIDsToUpdate] Entity IDs to update userData for.
*/
function saveUserData(entityIDsToUpdate) {
saveJSONUserData(true, entityIDsToUpdate);
}
function setJSONError(property, isError) {
$("#property-"+ property + "-editor").toggleClass('error', isError);
let $propertyUserDataEditorStatus = $("#property-"+ property + "-editorStatus");
$propertyUserDataEditorStatus.css('display', isError ? 'block' : 'none');
$propertyUserDataEditorStatus.text(isError ? 'Invalid JSON code - look for red X in your code' : '');
}
/**
* @param {boolean} noUpdate - don't update the UI, but do send a property update.
* @param {Set.<string>} [entityIDsToUpdate] - Entity IDs to update userData for.
*/
function setUserDataFromEditor(noUpdate, entityIDsToUpdate) {
let errorFound = false;
try {
editor.get();
} catch (e) {
errorFound = true;
}
setJSONError('userData', errorFound);
if (errorFound) {
return;
}
let text = editor.getText();
if (noUpdate) {
EventBridge.emitWebEvent(
JSON.stringify({
ids: [...entityIDsToUpdate],
type: "saveUserData",
properties: {
userData: text
}
})
);
} else {
updateProperty('userData', text, false);
}
}
let editor = null;
function createJSONEditor() {
let container = document.getElementById("property-userData-editor");
let options = {
search: false,
mode: 'tree',
modes: ['code', 'tree'],
name: 'userData',
onError: function(e) {
alert('JSON editor:' + e);
},
onChange: function() {
let currentJSONString = editor.getText();
if (currentJSONString === '{"":""}') {
return;
}
$('#property-userData-button-save').attr('disabled', false);
}
};
editor = new JSONEditor(container, options);
}
function showSaveUserDataButton() {
$('#property-userData-button-save').show();
}
function hideSaveUserDataButton() {
$('#property-userData-button-save').hide();
}
function disableSaveUserDataButton() {
$('#property-userData-button-save').attr('disabled', true);
}
function showNewJSONEditorButton() {
$('#property-userData-button-edit').show();
}
function hideNewJSONEditorButton() {
$('#property-userData-button-edit').hide();
}
function showUserDataTextArea() {
$('#property-userData').show();
}
function hideUserDataTextArea() {
$('#property-userData').hide();
}
function hideUserDataSaved() {
$('#property-userData-saved').hide();
}
function showStaticUserData() {
if (editor !== null) {
let $propertyUserDataStatic = $('#property-userData-static');
$propertyUserDataStatic.show();
$propertyUserDataStatic.css('height', $('#property-userData-editor').height());
$propertyUserDataStatic.text(editor.getText());
}
}
function removeStaticUserData() {
$('#property-userData-static').hide();
}
function setEditorJSON(json) {
editor.set(json);
if (editor.hasOwnProperty('expandAll')) {
editor.expandAll();
}
}
function deleteJSONEditor() {
if (editor !== null) {
setJSONError('userData', false);
editor.destroy();
editor = null;
}
}
let savedJSONTimer = null;
/**
* @param {boolean} noUpdate - don't update the UI, but do send a property update.
* @param {Set.<string>} [entityIDsToUpdate] Entity IDs to update userData for
*/
function saveJSONUserData(noUpdate, entityIDsToUpdate) {
setUserDataFromEditor(noUpdate, entityIDsToUpdate ? entityIDsToUpdate : selectedEntityIDs);
$('#property-userData-saved').show();
$('#property-userData-button-save').attr('disabled', true);
if (savedJSONTimer !== null) {
clearTimeout(savedJSONTimer);
}
savedJSONTimer = setTimeout(function() {
hideUserDataSaved();
}, EDITOR_TIMEOUT_DURATION);
}
/**
* MATERIAL DATA FUNCTIONS
*/
function clearMaterialData() {
let elMaterialData = getPropertyInputElement("materialData");
deleteJSONMaterialEditor();
elMaterialData.value = "";
showMaterialDataTextArea();
showNewJSONMaterialEditorButton();
hideSaveMaterialDataButton();
updateProperty('materialData', elMaterialData.value, false);
}
function newJSONMaterialEditor() {
getPropertyInputElement("materialData").classList.remove('multi-diff');
deleteJSONMaterialEditor();
createJSONMaterialEditor();
let data = {};
setMaterialEditorJSON(data);
hideMaterialDataTextArea();
hideNewJSONMaterialEditorButton();
showSaveMaterialDataButton();
}
function saveMaterialData() {
saveJSONMaterialData(true);
}
/**
* @param {boolean} noUpdate - don't update the UI, but do send a property update.
* @param {Set.<string>} [entityIDsToUpdate] - Entity IDs to update materialData for.
*/
function setMaterialDataFromEditor(noUpdate, entityIDsToUpdate) {
let errorFound = false;
try {
materialEditor.get();
} catch (e) {
errorFound = true;
}
setJSONError('materialData', errorFound);
if (errorFound) {
return;
}
let text = materialEditor.getText();
if (noUpdate) {
EventBridge.emitWebEvent(
JSON.stringify({
ids: [...entityIDsToUpdate],
type: "saveMaterialData",
properties: {
materialData: text
}
})
);
} else {
updateProperty('materialData', text, false);
}
}
let materialEditor = null;
function createJSONMaterialEditor() {
let container = document.getElementById("property-materialData-editor");
let options = {
search: false,
mode: 'tree',
modes: ['code', 'tree'],
name: 'materialData',
onError: function(e) {
alert('JSON editor:' + e);
},
onChange: function() {
let currentJSONString = materialEditor.getText();
if (currentJSONString === '{"":""}') {
return;
}
$('#property-materialData-button-save').attr('disabled', false);
}
};
materialEditor = new JSONEditor(container, options);
}
function showSaveMaterialDataButton() {
$('#property-materialData-button-save').show();
}
function hideSaveMaterialDataButton() {
$('#property-materialData-button-save').hide();
}
function disableSaveMaterialDataButton() {
$('#property-materialData-button-save').attr('disabled', true);
}
function showNewJSONMaterialEditorButton() {
$('#property-materialData-button-edit').show();
}
function hideNewJSONMaterialEditorButton() {
$('#property-materialData-button-edit').hide();
}
function showMaterialDataTextArea() {
$('#property-materialData').show();
}
function hideMaterialDataTextArea() {
$('#property-materialData').hide();
}
function hideMaterialDataSaved() {
$('#property-materialData-saved').hide();
}
function showStaticMaterialData() {
if (materialEditor !== null) {
let $propertyMaterialDataStatic = $('#property-materialData-static');
$propertyMaterialDataStatic.show();
$propertyMaterialDataStatic.css('height', $('#property-materialData-editor').height());
$propertyMaterialDataStatic.text(materialEditor.getText());
}
}
function removeStaticMaterialData() {
$('#property-materialData-static').hide();
}
function setMaterialEditorJSON(json) {
materialEditor.set(json);
if (materialEditor.hasOwnProperty('expandAll')) {
materialEditor.expandAll();
}
}
function deleteJSONMaterialEditor() {
if (materialEditor !== null) {
setJSONError('materialData', false);
materialEditor.destroy();
materialEditor = null;
}
}
let savedMaterialJSONTimer = null;
/**
* @param {boolean} noUpdate - don't update the UI, but do send a property update.
* @param {Set.<string>} [entityIDsToUpdate] - Entity IDs to update materialData for.
*/
function saveJSONMaterialData(noUpdate, entityIDsToUpdate) {
setMaterialDataFromEditor(noUpdate, entityIDsToUpdate ? entityIDsToUpdate : selectedEntityIDs);
$('#property-materialData-saved').show();
$('#property-materialData-button-save').attr('disabled', true);
if (savedMaterialJSONTimer !== null) {
clearTimeout(savedMaterialJSONTimer);
}
savedMaterialJSONTimer = setTimeout(function() {
hideMaterialDataSaved();
}, EDITOR_TIMEOUT_DURATION);
}
function bindAllNonJSONEditorElements() {
let inputs = $('input');
let i;
for (i = 0; i < inputs.length; ++i) {
let input = inputs[i];
let field = $(input);
// TODO FIXME: (JSHint) Functions declared within loops referencing
// an outer scoped variable may lead to confusing semantics.
field.on('focus', function(e) {
if (e.target.id === "property-userData-button-edit" || e.target.id === "property-userData-button-clear" ||
e.target.id === "property-materialData-button-edit" || e.target.id === "property-materialData-button-clear") {
return;
}
if ($('#property-userData-editor').css('height') !== "0px") {
saveUserData();
}
if ($('#property-materialData-editor').css('height') !== "0px") {
saveMaterialData();
}
});
}
}
/**
* DROPDOWN FUNCTIONS
*/
function setDropdownText(dropdown) {
let lis = dropdown.parentNode.getElementsByTagName("li");
let text = "";
for (let i = 0; i < lis.length; ++i) {
if (String(lis[i].getAttribute("value")) === String(dropdown.value)) {
text = lis[i].textContent;
}
}
dropdown.firstChild.textContent = text;
}
function toggleDropdown(event) {
let element = event.target;
if (element.nodeName !== "DT") {
element = element.parentNode;
}
element = element.parentNode;
let isDropped = element.getAttribute("dropped");
element.setAttribute("dropped", isDropped !== "true" ? "true" : "false");
}
function closeAllDropdowns() {
let elDropdowns = document.querySelectorAll("div.dropdown > dl");
for (let i = 0; i < elDropdowns.length; ++i) {
elDropdowns[i].setAttribute('dropped', 'false');
}
}
function setDropdownValue(event) {
let dt = event.target.parentNode.parentNode.previousSibling.previousSibling;
dt.value = event.target.getAttribute("value");
dt.firstChild.textContent = event.target.textContent;
dt.parentNode.setAttribute("dropped", "false");
let evt = document.createEvent("HTMLEvents");
evt.initEvent("change", true, true);
dt.dispatchEvent(evt);
}
/**
* TEXTAREA FUNCTIONS
*/
function setTextareaScrolling(element) {
let isScrolling = element.scrollHeight > element.offsetHeight;
element.setAttribute("scrolling", isScrolling ? "true" : "false");
}
/**
* MATERIAL TARGET FUNCTIONS
*/
function requestMaterialTarget() {
EventBridge.emitWebEvent(JSON.stringify({
type: 'materialTargetRequest',
entityID: getFirstSelectedID(),
}));
}
function setMaterialTargetData(materialTargetData) {
let elDivOptions = getPropertyInputElement("parentMaterialName");
resetDynamicMultiselectProperty(elDivOptions);
if (materialTargetData === undefined) {
return;
}
elDivOptions.firstChild.style.display = "none"; // hide "No Options" text
elDivOptions.parentNode.lastChild.style.display = null; // show Select/Clear all buttons
let numMeshes = materialTargetData.numMeshes;
for (let i = 0; i < numMeshes; ++i) {
addMaterialTarget(elDivOptions, i, false);
}
let materialNames = materialTargetData.materialNames;
let materialNamesAdded = [];
for (let i = 0; i < materialNames.length; ++i) {
let materialName = materialNames[i];
if (materialNamesAdded.indexOf(materialName) === -1) {
addMaterialTarget(elDivOptions, materialName, true);
materialNamesAdded.push(materialName);
}
}
materialTargetPropertyUpdate(elDivOptions.propertyValue);
}
function addMaterialTarget(elDivOptions, targetID, isMaterialName) {
let elementID = elDivOptions.getAttribute("id");
elementID += isMaterialName ? "-material-" : "-mesh-";
elementID += targetID;
let elDiv = document.createElement('div');
elDiv.className = "materialTargetDiv";
elDiv.onclick = onToggleMaterialTarget;
elDivOptions.appendChild(elDiv);
let elInput = document.createElement('input');
elInput.className = "materialTargetInput";
elInput.setAttribute("type", "checkbox");
elInput.setAttribute("id", elementID);
elInput.setAttribute("targetID", targetID);
elInput.setAttribute("isMaterialName", isMaterialName);
elDiv.appendChild(elInput);
let elLabel = document.createElement('label');
elLabel.setAttribute("for", elementID);
elLabel.innerText = isMaterialName ? "Material " + targetID : "Mesh Index " + targetID;
elDiv.appendChild(elLabel);
return elDiv;
}
function onToggleMaterialTarget(event) {
let elTarget = event.target;
if (elTarget instanceof HTMLInputElement) {
sendMaterialTargetProperty();
}
event.stopPropagation();
}
function setAllMaterialTargetInputs(checked) {
let elDivOptions = getPropertyInputElement("parentMaterialName");
let elInputs = elDivOptions.getElementsByClassName("materialTargetInput");
for (let i = 0; i < elInputs.length; ++i) {
elInputs[i].checked = checked;
}
}
function selectAllMaterialTarget() {
setAllMaterialTargetInputs(true);
sendMaterialTargetProperty();
}
function clearAllMaterialTarget() {
setAllMaterialTargetInputs(false);
sendMaterialTargetProperty();
}
function sendMaterialTargetProperty() {
let elDivOptions = getPropertyInputElement("parentMaterialName");
let elInputs = elDivOptions.getElementsByClassName("materialTargetInput");
let materialTargetList = [];
for (let i = 0; i < elInputs.length; ++i) {
let elInput = elInputs[i];
if (elInput.checked) {
let targetID = elInput.getAttribute("targetID");
if (elInput.getAttribute("isMaterialName") === "true") {
materialTargetList.push(MATERIAL_PREFIX_STRING + targetID);
} else {
materialTargetList.push(targetID);
}
}
}
let propertyValue = materialTargetList.join(",");
if (propertyValue.length > 1) {
propertyValue = "[" + propertyValue + "]";
}
updateProperty("parentMaterialName", propertyValue, false);
}
function materialTargetPropertyUpdate(propertyValue) {
let elDivOptions = getPropertyInputElement("parentMaterialName");
let elInputs = elDivOptions.getElementsByClassName("materialTargetInput");
if (propertyValue.startsWith('[')) {
propertyValue = propertyValue.substring(1, propertyValue.length);
}
if (propertyValue.endsWith(']')) {
propertyValue = propertyValue.substring(0, propertyValue.length - 1);
}
let materialTargets = propertyValue.split(",");
for (let i = 0; i < elInputs.length; ++i) {
let elInput = elInputs[i];
let targetID = elInput.getAttribute("targetID");
let materialTargetName = targetID;
if (elInput.getAttribute("isMaterialName") === "true") {
materialTargetName = MATERIAL_PREFIX_STRING + targetID;
}
elInput.checked = materialTargets.indexOf(materialTargetName) >= 0;
}
elDivOptions.propertyValue = propertyValue;
}
function roundAndFixNumber(number, propertyData) {
let result = number;
if (propertyData.round !== undefined) {
result = Math.round(result * propertyData.round) / propertyData.round;
}
if (propertyData.decimals !== undefined) {
return result.toFixed(propertyData.decimals)
}
return result;
}
function applyInputNumberPropertyModifiers(number, propertyData) {
const multiplier = propertyData.multiplier !== undefined ? propertyData.multiplier : 1;
return roundAndFixNumber(number / multiplier, propertyData);
}
function applyOutputNumberPropertyModifiers(number, propertyData) {
const multiplier = propertyData.multiplier !== undefined ? propertyData.multiplier : 1;
return roundAndFixNumber(number * multiplier, propertyData);
}
const areSetsEqual = (a, b) => a.size === b.size && [...a].every(value => b.has(value));
function handleEntitySelectionUpdate(selections, isPropertiesToolUpdate) {
const previouslySelectedEntityIDs = selectedEntityIDs;
currentSelections = selections;
selectedEntityIDs = new Set(selections.map(selection => selection.id));
const multipleSelections = currentSelections.length > 1;
const hasSelectedEntityChanged = !areSetsEqual(selectedEntityIDs, previouslySelectedEntityIDs);
if (selections.length === 0) {
deleteJSONEditor();
deleteJSONMaterialEditor();
resetProperties();
showGroupsForType("None");
let elIcon = properties.type.elSpan;
elIcon.innerText = NO_SELECTION;
elIcon.style.display = 'inline-block';
getPropertyInputElement("userData").value = "";
showUserDataTextArea();
showSaveUserDataButton();
showNewJSONEditorButton();
getPropertyInputElement("materialData").value = "";
showMaterialDataTextArea();
showSaveMaterialDataButton();
showNewJSONMaterialEditorButton();
disableProperties();
} else {
if (!isPropertiesToolUpdate && !hasSelectedEntityChanged && document.hasFocus()) {
// in case the selection has not changed and we still have focus on the properties page,
// we will ignore the event.
return;
}
if (hasSelectedEntityChanged) {
if (!multipleSelections) {
resetServerScriptStatus();
}
}
const doSelectElement = !hasSelectedEntityChanged;
// Get unique entity types, and convert the types Sphere and Box to Shape
const shapeTypes = ["Sphere", "Box"];
const entityTypes = [...new Set(currentSelections.map(a =>
shapeTypes.includes(a.properties.type) ? "Shape" : a.properties.type))];
const shownGroups = getGroupsForTypes(entityTypes);
showGroupsForTypes(entityTypes);
const lockedMultiValue = getMultiplePropertyValue('locked');
if (lockedMultiValue.isMultiDiffValue || lockedMultiValue.value) {
disableProperties();
getPropertyInputElement('locked').removeAttribute('disabled');
} else {
enableProperties();
disableSaveUserDataButton();
disableSaveMaterialDataButton()
}
const certificateIDMultiValue = getMultiplePropertyValue('certificateID');
const hasCertifiedInSelection = certificateIDMultiValue.isMultiDiffValue || certificateIDMultiValue.value !== "";
Object.entries(properties).forEach(function([propertyID, property]) {
const propertyData = property.data;
const propertyName = property.name;
let propertyMultiValue = getMultiplePropertyValue(propertyName);
let isMultiDiffValue = propertyMultiValue.isMultiDiffValue;
let propertyValue = propertyMultiValue.value;
if (propertyData.selectionVisibility !== undefined) {
let visibility = propertyData.selectionVisibility;
let propertyVisible = true;
if (!multipleSelections) {
propertyVisible = isFlagSet(visibility, PROPERTY_SELECTION_VISIBILITY.SINGLE_SELECTION);
} else if (isMultiDiffValue) {
propertyVisible = isFlagSet(visibility, PROPERTY_SELECTION_VISIBILITY.MULTI_DIFF_SELECTIONS);
} else {
propertyVisible = isFlagSet(visibility, PROPERTY_SELECTION_VISIBILITY.MULTIPLE_SELECTIONS);
}
setPropertyVisibility(property, propertyVisible);
}
const isSubProperty = propertyData.subPropertyOf !== undefined;
if (propertyValue === undefined && !isMultiDiffValue && !isSubProperty) {
return;
}
if (!shownGroups.includes(property.group_id)) {
const WANT_DEBUG_SHOW_HIDDEN_FROM_GROUPS = false;
if (WANT_DEBUG_SHOW_HIDDEN_FROM_GROUPS) {
console.log("Skipping property " + property.data.label + " [" + property.name +
"] from hidden group " + property.group_id);
}
return;
}
if (propertyData.hideIfCertified && hasCertifiedInSelection) {
propertyValue = "** Certified **";
property.elInput.disabled = true;
}
if (propertyName === "type") {
propertyValue = entityTypes.length > 1 ? "Multiple" : propertyMultiValue.values[0];
}
switch (propertyData.type) {
case 'string': {
if (isMultiDiffValue) {
if (propertyData.readOnly && propertyData.multiDisplayMode
&& propertyData.multiDisplayMode === PROPERTY_MULTI_DISPLAY_MODE.COMMA_SEPARATED_VALUES) {
property.elInput.value = propertyMultiValue.values.join(", ");
} else {
property.elInput.classList.add('multi-diff');
property.elInput.value = "";
}
} else {
property.elInput.classList.remove('multi-diff');
property.elInput.value = propertyValue;
}
break;
}
case 'bool': {
const inverse = propertyData.inverse !== undefined ? propertyData.inverse : false;
if (isSubProperty) {
let subPropertyMultiValue = getMultiplePropertyValue(propertyData.subPropertyOf);
let propertyValue = subPropertyMultiValue.value;
isMultiDiffValue = subPropertyMultiValue.isMultiDiffValue;
if (isMultiDiffValue) {
let detailedSubProperty = getDetailedSubPropertyMPVDiff(subPropertyMultiValue, propertyName);
property.elInput.checked = detailedSubProperty.isChecked;
property.elInput.classList.toggle('multi-diff', detailedSubProperty.isMultiDiff);
} else {
let subProperties = propertyValue.split(",");
let subPropertyValue = subProperties.indexOf(propertyName) > -1;
property.elInput.checked = inverse ? !subPropertyValue : subPropertyValue;
property.elInput.classList.remove('multi-diff');
}
} else {
if (isMultiDiffValue) {
property.elInput.checked = false;
} else {
property.elInput.checked = inverse ? !propertyValue : propertyValue;
}
property.elInput.classList.toggle('multi-diff', isMultiDiffValue);
}
break;
}
case 'number': {
property.elInput.value = isMultiDiffValue ? "" : propertyValue;
property.elInput.classList.toggle('multi-diff', isMultiDiffValue);
break;
}
case 'number-draggable': {
let detailedNumberDiff = getDetailedNumberMPVDiff(propertyMultiValue, propertyData);
property.elNumber.setValue(detailedNumberDiff.averagePerPropertyComponent[0], detailedNumberDiff.propertyComponentDiff[0]);
break;
}
case 'rect': {
let detailedNumberDiff = getDetailedNumberMPVDiff(propertyMultiValue, propertyData);
property.elNumberX.setValue(detailedNumberDiff.averagePerPropertyComponent.x, detailedNumberDiff.propertyComponentDiff.x);
property.elNumberY.setValue(detailedNumberDiff.averagePerPropertyComponent.y, detailedNumberDiff.propertyComponentDiff.y);
property.elNumberWidth.setValue(detailedNumberDiff.averagePerPropertyComponent.width, detailedNumberDiff.propertyComponentDiff.width);
property.elNumberHeight.setValue(detailedNumberDiff.averagePerPropertyComponent.height, detailedNumberDiff.propertyComponentDiff.height);
break;
}
case 'vec3':
case 'vec2': {
let detailedNumberDiff = getDetailedNumberMPVDiff(propertyMultiValue, propertyData);
property.elNumberX.setValue(detailedNumberDiff.averagePerPropertyComponent.x, detailedNumberDiff.propertyComponentDiff.x);
property.elNumberY.setValue(detailedNumberDiff.averagePerPropertyComponent.y, detailedNumberDiff.propertyComponentDiff.y);
if (property.elNumberZ !== undefined) {
property.elNumberZ.setValue(detailedNumberDiff.averagePerPropertyComponent.z, detailedNumberDiff.propertyComponentDiff.z);
}
break;
}
case 'color': {
let displayColor = propertyMultiValue.isMultiDiffValue ? propertyMultiValue.values[0] : propertyValue;
property.elColorPicker.style.backgroundColor = "rgb(" + displayColor.red + "," +
displayColor.green + "," +
displayColor.blue + ")";
property.elColorPicker.classList.toggle('multi-diff', propertyMultiValue.isMultiDiffValue);
if (hasSelectedEntityChanged && $(property.elColorPicker).attr('active') === 'true') {
// Set the color picker inactive before setting the color,
// otherwise an update will be sent directly after setting it here.
$(property.elColorPicker).attr('active', 'false');
colorPickers['#' + property.elementID].colpickSetColor({
"r": displayColor.red,
"g": displayColor.green,
"b": displayColor.blue
});
$(property.elColorPicker).attr('active', 'true');
}
property.elNumberR.setValue(displayColor.red);
property.elNumberG.setValue(displayColor.green);
property.elNumberB.setValue(displayColor.blue);
break;
}
case 'dropdown': {
property.elInput.classList.toggle('multi-diff', isMultiDiffValue);
property.elInput.value = isMultiDiffValue ? "" : propertyValue;
setDropdownText(property.elInput);
break;
}
case 'textarea': {
property.elInput.value = propertyValue;
setTextareaScrolling(property.elInput);
break;
}
case 'icon': {
property.elSpan.innerHTML = propertyData.icons[propertyValue];
property.elSpan.style.display = "inline-block";
break;
}
case 'texture': {
property.elInput.value = isMultiDiffValue ? "" : propertyValue;
property.elInput.classList.toggle('multi-diff', isMultiDiffValue);
if (isMultiDiffValue) {
property.elInput.setMultipleValues();
} else {
property.elInput.imageLoad(property.elInput.value);
}
break;
}
case 'dynamic-multiselect': {
if (!isMultiDiffValue && property.data.propertyUpdate) {
property.data.propertyUpdate(propertyValue);
}
break;
}
}
let showPropertyRules = property.showPropertyRules;
if (showPropertyRules !== undefined) {
for (let propertyToShow in showPropertyRules) {
let showIfThisPropertyValue = showPropertyRules[propertyToShow];
let show = String(propertyValue) === String(showIfThisPropertyValue);
showPropertyElement(propertyToShow, show);
}
}
});
updateVisibleSpaceModeProperties();
let userDataMultiValue = getMultiplePropertyValue("userData");
let userDataTextArea = getPropertyInputElement("userData");
let json = null;
if (!userDataMultiValue.isMultiDiffValue) {
try {
json = JSON.parse(userDataMultiValue.value);
} catch (e) {
}
}
if (json !== null) {
if (editor === null) {
createJSONEditor();
}
userDataTextArea.classList.remove('multi-diff');
setEditorJSON(json);
showSaveUserDataButton();
hideUserDataTextArea();
hideNewJSONEditorButton();
hideUserDataSaved();
} else {
// normal text
deleteJSONEditor();
userDataTextArea.classList.toggle('multi-diff', userDataMultiValue.isMultiDiffValue);
userDataTextArea.value = userDataMultiValue.isMultiDiffValue ? "" : userDataMultiValue.value;
showUserDataTextArea();
showNewJSONEditorButton();
hideSaveUserDataButton();
hideUserDataSaved();
}
let materialDataMultiValue = getMultiplePropertyValue("materialData");
let materialDataTextArea = getPropertyInputElement("materialData");
let materialJson = null;
if (!materialDataMultiValue.isMultiDiffValue) {
try {
materialJson = JSON.parse(materialDataMultiValue.value);
} catch (e) {
}
}
if (materialJson !== null) {
if (materialEditor === null) {
createJSONMaterialEditor();
}
materialDataTextArea.classList.remove('multi-diff');
setMaterialEditorJSON(materialJson);
showSaveMaterialDataButton();
hideMaterialDataTextArea();
hideNewJSONMaterialEditorButton();
hideMaterialDataSaved();
} else {
// normal text
deleteJSONMaterialEditor();
materialDataTextArea.classList.toggle('multi-diff', materialDataMultiValue.isMultiDiffValue);
materialDataTextArea.value = materialDataMultiValue.isMultiDiffValue ? "" : materialDataMultiValue.value;
showMaterialDataTextArea();
showNewJSONMaterialEditorButton();
hideSaveMaterialDataButton();
hideMaterialDataSaved();
}
if (hasSelectedEntityChanged && selections.length === 1 && entityTypes[0] === "Material") {
requestMaterialTarget();
}
let activeElement = document.activeElement;
if (doSelectElement && typeof activeElement.select !== "undefined") {
activeElement.select();
}
}
}
function loaded() {
openEventBridge(function() {
let elPropertiesList = document.getElementById("properties-list");
GROUPS.forEach(function(group) {
let elGroup;
if (group.addToGroup !== undefined) {
let fieldset = document.getElementById("properties-" + group.addToGroup);
elGroup = document.createElement('div');
fieldset.appendChild(elGroup);
} else {
elGroup = document.createElement('div');
elGroup.className = 'section ' + (group.isMinor ? "minor" : "major");
elGroup.setAttribute("id", "properties-" + group.id);
elPropertiesList.appendChild(elGroup);
}
if (group.label !== undefined) {
let elLegend = document.createElement('div');
elLegend.className = "section-header";
elLegend.appendChild(createElementFromHTML(`<div class="label">${group.label}</div>`));
let elSpan = document.createElement('span');
elSpan.className = "collapse-icon";
elSpan.innerText = "M";
elLegend.appendChild(elSpan);
elGroup.appendChild(elLegend);
}
group.properties.forEach(function(propertyData) {
let propertyType = propertyData.type;
let propertyID = propertyData.propertyID;
let propertyName = propertyData.propertyName !== undefined ? propertyData.propertyName : propertyID;
let propertySpaceMode = propertyData.spaceMode !== undefined ? propertyData.spaceMode : PROPERTY_SPACE_MODE.ALL;
let propertyElementID = "property-" + propertyID;
propertyElementID = propertyElementID.replace('.', '-');
let elContainer, elLabel;
if (propertyData.replaceID === undefined) {
// Create subheader, or create new property and append it.
if (propertyType === "sub-header") {
elContainer = createElementFromHTML(
`<div class="sub-section-header legend">${propertyData.label}</div>`);
} else {
elContainer = document.createElement('div');
elContainer.setAttribute("id", "div-" + propertyElementID);
elContainer.className = 'property container';
}
if (group.twoColumn && propertyData.column !== undefined && propertyData.column !== -1) {
let columnName = group.id + "column" + propertyData.column;
let elColumn = document.getElementById(columnName);
if (!elColumn) {
let columnDivName = group.id + "columnDiv";
let elColumnDiv = document.getElementById(columnDivName);
if (!elColumnDiv) {
elColumnDiv = document.createElement('div');
elColumnDiv.className = "two-column";
elColumnDiv.setAttribute("id", group.id + "columnDiv");
elGroup.appendChild(elColumnDiv);
}
elColumn = document.createElement('fieldset');
elColumn.className = "column";
elColumn.setAttribute("id", columnName);
elColumnDiv.appendChild(elColumn);
}
elColumn.appendChild(elContainer);
} else {
elGroup.appendChild(elContainer);
}
let labelText = propertyData.label !== undefined ? propertyData.label : "";
let className = '';
if (propertyData.indentedLabel || propertyData.showPropertyRule !== undefined) {
className = 'indented';
}
elLabel = createElementFromHTML(
`<label><span class="${className}">${labelText}</span></label>`);
elContainer.appendChild(elLabel);
} else {
elContainer = document.getElementById(propertyData.replaceID);
}
if (elLabel) {
createAppTooltip.registerTooltipElement(elLabel.childNodes[0], propertyID, propertyName);
}
let elProperty = createElementFromHTML('<div style="width: 100%;"></div>');
elContainer.appendChild(elProperty);
if (propertyType === 'triple') {
elProperty.className = 'flex-row';
for (let i = 0; i < propertyData.properties.length; ++i) {
let innerPropertyData = propertyData.properties[i];
let elWrapper = createElementFromHTML('<div class="triple-item"></div>');
elProperty.appendChild(elWrapper);
let propertyID = innerPropertyData.propertyID;
let propertyName = innerPropertyData.propertyName !== undefined ? innerPropertyData.propertyName : propertyID;
let propertyElementID = "property-" + propertyID;
propertyElementID = propertyElementID.replace('.', '-');
let property = createProperty(innerPropertyData, propertyElementID, propertyName, propertyID, elWrapper);
property.isParticleProperty = group.id.includes("particles");
property.elContainer = elContainer;
property.spaceMode = propertySpaceMode;
property.group_id = group.id;
let elLabel = createElementFromHTML(`<div class="triple-label">${innerPropertyData.label}</div>`);
createAppTooltip.registerTooltipElement(elLabel, propertyID, propertyName);
elWrapper.appendChild(elLabel);
if (property.type !== 'placeholder') {
properties[propertyID] = property;
}
if (innerPropertyData.type === 'number' || innerPropertyData.type === 'number-draggable') {
propertyRangeRequests.push(propertyID);
}
}
} else {
let property = createProperty(propertyData, propertyElementID, propertyName, propertyID, elProperty);
property.isParticleProperty = group.id.includes("particles");
property.elContainer = elContainer;
property.spaceMode = propertySpaceMode;
property.group_id = group.id;
if (property.type !== 'placeholder') {
properties[propertyID] = property;
}
if (propertyData.type === 'number' || propertyData.type === 'number-draggable' ||
propertyData.type === 'vec2' || propertyData.type === 'vec3' || propertyData.type === 'rect') {
propertyRangeRequests.push(propertyID);
}
let showPropertyRule = propertyData.showPropertyRule;
if (showPropertyRule !== undefined) {
let dependentProperty = Object.keys(showPropertyRule)[0];
let dependentPropertyValue = showPropertyRule[dependentProperty];
if (properties[dependentProperty] === undefined) {
properties[dependentProperty] = {};
}
if (properties[dependentProperty].showPropertyRules === undefined) {
properties[dependentProperty].showPropertyRules = {};
}
properties[dependentProperty].showPropertyRules[propertyID] = dependentPropertyValue;
}
}
});
elGroups[group.id] = elGroup;
});
let minorSections = document.querySelectorAll(".section.minor");
minorSections[minorSections.length - 1].className += " last";
updateVisibleSpaceModeProperties();
if (window.EventBridge !== undefined) {
EventBridge.scriptEventReceived.connect(function(data) {
data = JSON.parse(data);
if (data.type === "server_script_status" && selectedEntityIDs.size === 1) {
let elServerScriptError = document.getElementById("property-serverScripts-error");
let elServerScriptStatus = document.getElementById("property-serverScripts-status");
elServerScriptError.value = data.errorInfo;
// If we just set elServerScriptError's display to block or none, we still end up with
// it's parent contributing 21px bottom padding even when elServerScriptError is display:none.
// So set it's parent to block or none
elServerScriptError.parentElement.style.display = data.errorInfo ? "block" : "none";
if (data.statusRetrieved === false) {
elServerScriptStatus.innerText = "Failed to retrieve status";
} else if (data.isRunning) {
elServerScriptStatus.innerText = ENTITY_SCRIPT_STATUS[data.status] || data.status;
} else {
elServerScriptStatus.innerText = NOT_RUNNING_SCRIPT_STATUS;
}
} else if (data.type === "update" && data.selections) {
if (data.spaceMode !== undefined) {
currentSpaceMode = data.spaceMode === "local" ? PROPERTY_SPACE_MODE.LOCAL : PROPERTY_SPACE_MODE.WORLD;
}
handleEntitySelectionUpdate(data.selections, data.isPropertiesToolUpdate);
} else if (data.type === 'tooltipsReply') {
createAppTooltip.setIsEnabled(!data.hmdActive);
createAppTooltip.setTooltipData(data.tooltips);
} else if (data.type === 'hmdActiveChanged') {
createAppTooltip.setIsEnabled(!data.hmdActive);
} else if (data.type === 'setSpaceMode') {
currentSpaceMode = data.spaceMode === "local" ? PROPERTY_SPACE_MODE.LOCAL : PROPERTY_SPACE_MODE.WORLD;
updateVisibleSpaceModeProperties();
} else if (data.type === 'propertyRangeReply') {
let propertyRanges = data.propertyRanges;
for (let property in propertyRanges) {
let propertyRange = propertyRanges[property];
if (propertyRange !== undefined) {
let propertyData = properties[property].data;
let multiplier = propertyData.multiplier;
if (propertyData.min === undefined && propertyRange.minimum !== "") {
propertyData.min = propertyRange.minimum;
if (multiplier !== undefined) {
propertyData.min /= multiplier;
}
}
if (propertyData.max === undefined && propertyRange.maximum !== "") {
propertyData.max = propertyRange.maximum;
if (multiplier !== undefined) {
propertyData.max /= multiplier;
}
}
switch (propertyData.type) {
case 'number':
updateNumberMinMax(properties[property]);
break;
case 'number-draggable':
updateNumberDraggableMinMax(properties[property]);
break;
case 'vec3':
case 'vec2':
updateVectorMinMax(properties[property]);
break;
case 'rect':
updateRectMinMax(properties[property]);
break;
}
}
}
} else if (data.type === 'materialTargetReply') {
if (data.entityID === getFirstSelectedID()) {
setMaterialTargetData(data.materialTargetData);
}
}
});
// Request tooltips and property ranges as soon as we can process a reply:
EventBridge.emitWebEvent(JSON.stringify({ type: 'tooltipsRequest' }));
EventBridge.emitWebEvent(JSON.stringify({ type: 'propertyRangeRequest', properties: propertyRangeRequests }));
}
// Server Script Status
let elServerScriptStatusOuter = document.getElementById('div-property-serverScriptStatus');
let elServerScriptStatusContainer = document.getElementById('div-property-serverScriptStatus').childNodes[1];
let serverScriptStatusElementID = 'property-serverScripts-status';
createAppTooltip.registerTooltipElement(elServerScriptStatusOuter.childNodes[0], "serverScriptsStatus");
let elServerScriptStatus = document.createElement('div');
elServerScriptStatus.setAttribute("id", serverScriptStatusElementID);
elServerScriptStatusContainer.appendChild(elServerScriptStatus);
// Server Script Error
let elServerScripts = getPropertyInputElement("serverScripts");
let elDiv = document.createElement('div');
elDiv.className = "property";
let elServerScriptError = document.createElement('textarea');
let serverScriptErrorElementID = 'property-serverScripts-error';
elServerScriptError.setAttribute("id", serverScriptErrorElementID);
elDiv.appendChild(elServerScriptError);
elServerScriptStatusContainer.appendChild(elDiv);
let elScript = getPropertyInputElement("script");
elScript.parentNode.className = "url refresh";
elServerScripts.parentNode.className = "url refresh";
// User Data
let userDataProperty = properties["userData"];
let elUserData = userDataProperty.elInput;
let userDataElementID = userDataProperty.elementID;
elDiv = elUserData.parentNode;
let elStaticUserData = document.createElement('div');
elStaticUserData.setAttribute("id", userDataElementID + "-static");
let elUserDataEditor = document.createElement('div');
elUserDataEditor.setAttribute("id", userDataElementID + "-editor");
let elUserDataEditorStatus = document.createElement('div');
elUserDataEditorStatus.setAttribute("id", userDataElementID + "-editorStatus");
let elUserDataSaved = document.createElement('span');
elUserDataSaved.setAttribute("id", userDataElementID + "-saved");
elUserDataSaved.innerText = "Saved!";
elDiv.childNodes[JSON_EDITOR_ROW_DIV_INDEX].appendChild(elUserDataSaved);
elDiv.insertBefore(elStaticUserData, elUserData);
elDiv.insertBefore(elUserDataEditor, elUserData);
elDiv.insertBefore(elUserDataEditorStatus, elUserData);
// Material Data
let materialDataProperty = properties["materialData"];
let elMaterialData = materialDataProperty.elInput;
let materialDataElementID = materialDataProperty.elementID;
elDiv = elMaterialData.parentNode;
let elStaticMaterialData = document.createElement('div');
elStaticMaterialData.setAttribute("id", materialDataElementID + "-static");
let elMaterialDataEditor = document.createElement('div');
elMaterialDataEditor.setAttribute("id", materialDataElementID + "-editor");
let elMaterialDataEditorStatus = document.createElement('div');
elMaterialDataEditorStatus.setAttribute("id", materialDataElementID + "-editorStatus");
let elMaterialDataSaved = document.createElement('span');
elMaterialDataSaved.setAttribute("id", materialDataElementID + "-saved");
elMaterialDataSaved.innerText = "Saved!";
elDiv.childNodes[JSON_EDITOR_ROW_DIV_INDEX].appendChild(elMaterialDataSaved);
elDiv.insertBefore(elStaticMaterialData, elMaterialData);
elDiv.insertBefore(elMaterialDataEditor, elMaterialData);
elDiv.insertBefore(elMaterialDataEditorStatus, elMaterialData);
// Collapsible sections
let elCollapsible = document.getElementsByClassName("collapse-icon");
let toggleCollapsedEvent = function(event) {
let element = this.parentNode.parentNode;
let isCollapsed = element.dataset.collapsed !== "true";
element.dataset.collapsed = isCollapsed ? "true" : false;
element.setAttribute("collapsed", isCollapsed ? "true" : "false");
this.textContent = isCollapsed ? "L" : "M";
};
for (let collapseIndex = 0, numCollapsibles = elCollapsible.length; collapseIndex < numCollapsibles; ++collapseIndex) {
let curCollapsibleElement = elCollapsible[collapseIndex];
curCollapsibleElement.addEventListener("click", toggleCollapsedEvent, true);
}
// Textarea scrollbars
let elTextareas = document.getElementsByTagName("TEXTAREA");
let textareaOnChangeEvent = function(event) {
setTextareaScrolling(event.target);
};
for (let textAreaIndex = 0, numTextAreas = elTextareas.length; textAreaIndex < numTextAreas; ++textAreaIndex) {
let curTextAreaElement = elTextareas[textAreaIndex];
setTextareaScrolling(curTextAreaElement);
curTextAreaElement.addEventListener("input", textareaOnChangeEvent, false);
curTextAreaElement.addEventListener("change", textareaOnChangeEvent, false);
/* FIXME: Detect and update textarea scrolling attribute on resize. Unfortunately textarea doesn't have a resize
event; mouseup is a partial stand-in but doesn't handle resizing if mouse moves outside textarea rectangle. */
curTextAreaElement.addEventListener("mouseup", textareaOnChangeEvent, false);
}
// Dropdowns
// For each dropdown the following replacement is created in place of the original dropdown...
// Structure created:
// <dl dropped="true/false">
// <dt name="?" id="?" value="?"><span>display text</span><span>carat</span></dt>
// <dd>
// <ul>
// <li value="??>display text</li>
// <li>...</li>
// </ul>
// </dd>
// </dl>
let elDropdowns = document.getElementsByTagName("select");
for (let dropDownIndex = 0; dropDownIndex < elDropdowns.length; ++dropDownIndex) {
let elDropdown = elDropdowns[dropDownIndex];
let options = elDropdown.getElementsByTagName("option");
let selectedOption = 0;
for (let optionIndex = 0; optionIndex < options.length; ++optionIndex) {
if (options[optionIndex].getAttribute("selected") === "selected") {
selectedOption = optionIndex;
break;
}
}
let div = elDropdown.parentNode;
let dl = document.createElement("dl");
div.appendChild(dl);
let dt = document.createElement("dt");
dt.name = elDropdown.name;
dt.id = elDropdown.id;
dt.addEventListener("click", toggleDropdown, true);
dl.appendChild(dt);
let elMultiDiff = document.createElement('span');
elMultiDiff.className = "multi-diff";
dl.appendChild(elMultiDiff);
let span = document.createElement("span");
span.setAttribute("value", options[selectedOption].value);
span.textContent = options[selectedOption].firstChild.textContent;
dt.appendChild(span);
let spanCaratDown = document.createElement("span");
spanCaratDown.textContent = "5"; // caratDn
dt.appendChild(spanCaratDown);
let dd = document.createElement("dd");
dl.appendChild(dd);
let ul = document.createElement("ul");
dd.appendChild(ul);
for (let listOptionIndex = 0; listOptionIndex < options.length; ++listOptionIndex) {
let li = document.createElement("li");
li.setAttribute("value", options[listOptionIndex].value);
li.textContent = options[listOptionIndex].firstChild.textContent;
li.addEventListener("click", setDropdownValue);
ul.appendChild(li);
}
let propertyID = elDropdown.getAttribute("propertyID");
let property = properties[propertyID];
property.elInput = dt;
dt.addEventListener('change', createEmitTextPropertyUpdateFunction(property));
}
document.addEventListener('click', function(ev) { closeAllDropdowns() }, true);
elDropdowns = document.getElementsByTagName("select");
while (elDropdowns.length > 0) {
let el = elDropdowns[0];
el.parentNode.removeChild(el);
elDropdowns = document.getElementsByTagName("select");
}
const KEY_CODES = {
BACKSPACE: 8,
DELETE: 46
};
document.addEventListener("keyup", function (keyUpEvent) {
const FILTERED_NODE_NAMES = ["INPUT", "TEXTAREA"];
if (FILTERED_NODE_NAMES.includes(keyUpEvent.target.nodeName)) {
return;
}
if (elUserDataEditor.contains(keyUpEvent.target) || elMaterialDataEditor.contains(keyUpEvent.target)) {
return;
}
let {code, key, keyCode, altKey, ctrlKey, metaKey, shiftKey} = keyUpEvent;
let controlKey = window.navigator.platform.startsWith("Mac") ? metaKey : ctrlKey;
let keyCodeString;
switch (keyCode) {
case KEY_CODES.DELETE:
keyCodeString = "Delete";
break;
case KEY_CODES.BACKSPACE:
keyCodeString = "Backspace";
break;
default:
keyCodeString = String.fromCharCode(keyUpEvent.keyCode);
break;
}
EventBridge.emitWebEvent(JSON.stringify({
type: 'keyUpEvent',
keyUpEvent: {
code,
key,
keyCode,
keyCodeString,
altKey,
controlKey,
shiftKey,
}
}));
}, false);
window.onblur = function() {
// Fake a change event
let ev = document.createEvent("HTMLEvents");
ev.initEvent("change", true, true);
document.activeElement.dispatchEvent(ev);
};
// For input and textarea elements, select all of the text on focus
let els = document.querySelectorAll("input, textarea");
for (let i = 0; i < els.length; ++i) {
els[i].onfocus = function (e) {
e.target.select();
};
}
bindAllNonJSONEditorElements();
showGroupsForType("None");
resetProperties();
disableProperties();
});
augmentSpinButtons();
disableDragDrop();
// Disable right-click context menu which is not visible in the HMD and makes it seem like the app has locked
document.addEventListener("contextmenu", function(event) {
event.preventDefault();
}, false);
setTimeout(function() {
EventBridge.emitWebEvent(JSON.stringify({ type: 'propertiesPageReady' }));
}, 1000);
}