// entityProperties.js // // Created by Ryan Huffman on November 13th, 2014 // Copyright 2014 High Fidelity, Inc. // Copyright 2020 Vircadia contributors. // Copyright 2022-2025 Overte e.V. // // 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, _, $ */ var currentTab = "base"; const DEGREES_TO_RADIANS = Math.PI / 180.0; const UUID_NONE = "{00000000-0000-0000-0000-000000000000}"; const ENTITY_HOST_TYPE_COLOR_DOMAIN = "#afafaf"; const ENTITY_HOST_TYPE_COLOR_AVATAR = "#7fdb98"; const ENTITY_HOST_TYPE_COLOR_LOCAL = "#f0d769"; const NO_SELECTION = ","; const MAX_TAGS_PER_ROWS = 5; const AUDIO_ATTENUATION_COEFFICIENT_DEFAULT_VALUE = 0.5; 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", label: "ENTITY", 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", buttons: [ { id: "navigateToParentEntity", label: "1", className: "navigation", onClick: navigateToSpecificEntityFromParentID } ], 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", }, { label: "Billboard Mode", type: "dropdown", options: { none: "None", yaw: "Yaw", full: "Full" }, propertyID: "billboardMode", }, { label: "Mirror Mode", type: "dropdown", options: { none: "None", mirror: "Mirror", portal: "Portal" }, propertyID: "mirrorMode", }, { label: "Portal Exit", type: "string", propertyID: "portalExitID", showPropertyRule: { "mirrorMode": "portal" }, }, { label: "Render With Zones", type: "multipleZonesSelection", propertyID: "renderWithZones", }, { label: "Tags", type: "arrayOfStrings", propertyID: "tags", useStringColor: true, }, ] }, { id: "shape", label: "SHAPE", 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", Torus: "Torus" }, propertyID: "shape", }, { label: "Inner Radius", type: "number-draggable", min: 0, max: 0.25, step: 0.01, decimals: 2, propertyID: "innerRadius", showPropertyRule: { "shape": "Torus" }, }, { label: "Color", type: "color", propertyID: "color", }, { label: "Alpha", type: "number-draggable", min: 0, max: 1, step: 0.01, decimals: 2, propertyID: "shapeAlpha", propertyName: "alpha", }, { label: "Unlit", type: "bool", propertyID: "shapeUnlit", propertyName: "unlit" } ] }, { id: "text", label: "TEXT", properties: [ { label: "Text", type: "textarea", 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: "Font", type: "string", propertyID: "font", }, { label: "Effect", type: "dropdown", options: { none: "None", outline: "Outline", "outline fill": "Outline with fill", shadow: "Shadow" }, propertyID: "textEffect", }, { label: "Effect Color", type: "color", propertyID: "textEffectColor", }, { label: "Effect Thickness", type: "number-draggable", min: 0.0, max: 0.5, step: 0.01, decimals: 2, propertyID: "textEffectThickness", }, { label: "Alignment", type: "dropdown", options: { left: "Left", center: "Center", right: "Right" }, propertyID: "textAlignment", propertyName: "alignment", // actual entity property name }, { label: "Vertical Alignment", type: "dropdown", options: { top: "Top", center: "Center", bottom: "Bottom" }, propertyID: "textVerticalAlignment", propertyName: "verticalAlignment", // 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", }, { label: "Unlit", type: "bool", propertyID: "unlit", } ] }, { id: "zone", label: "ZONE", properties: [ { label: "Shape Type", type: "dropdown", options: { "box": "Box", "sphere": "Sphere", "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 placeholder: "URL", }, { label: "Flying Allowed", type: "bool", propertyID: "flyingAllowed", }, { label: "Ghosting Allowed", type: "bool", propertyID: "ghostingAllowed", }, { label: "Filter", type: "string", propertyID: "filterURL", placeholder: "URL", } ] }, { id: "zone_key_light", label: "ZONE KEY LIGHT", properties: [ { 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: -40, 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: "Shadow Bias", type: "number-draggable", min: 0, max: 1, step: 0.01, decimals: 2, propertyID: "keyLight.shadowBias", showPropertyRule: { "keyLightMode": "enabled" }, }, { label: "Shadow Max Distance", type: "number-draggable", min: 0, max: 250, step: 0.1, decimals: 2, propertyID: "keyLight.shadowMaxDistance", showPropertyRule: { "keyLightMode": "enabled" }, } ] }, { id: "zone_skybox", label: "ZONE SKYBOX", properties: [ { 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" }, placeholder: "URL", } ] }, { id: "zone_ambient_light", label: "ZONE AMBIENT LIGHT", properties: [ { label: "Ambient Light", type: "dropdown", options: { inherit: "Inherit", disabled: "Off", enabled: "On" }, propertyID: "ambientLightMode", }, { label: "Ambient Color", type: "color", propertyID: "ambientLight.ambientColor", showPropertyRule: { "ambientLightMode": "enabled" }, }, { type: "buttons", buttons: [ { id: "copy", label: "Copy color from Skybox", className: "black", onClick: copySkyboxColorToAmbientColor } ], propertyID: "copyColorToAmbient", showPropertyRule: { "ambientLightMode": "enabled" }, }, { label: "Ambient Intensity", type: "number-draggable", min: -200, max: 200, step: 0.1, decimals: 2, propertyID: "ambientLight.ambientIntensity", showPropertyRule: { "ambientLightMode": "enabled" }, }, { label: "Ambient Source", type: "string", propertyID: "ambientLight.ambientURL", showPropertyRule: { "ambientLightMode": "enabled" }, placeholder: "URL", }, { type: "buttons", buttons: [ { id: "copy", label: "Copy URL from Skybox", className: "black", onClick: copySkyboxURLToAmbientURL } ], propertyID: "copyURLToAmbient", showPropertyRule: { "ambientLightMode": "enabled" }, } ] }, { id: "zone_haze", label: "ZONE HAZE", properties: [ { 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: -16000, max: 16000, step: 1, decimals: 0, unit: "m", propertyID: "haze.hazeBaseRef", showPropertyRule: { "hazeMode": "enabled" }, }, { label: "Ceiling", type: "number-draggable", min: -16000, max: 16000, 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" }, } ] }, { id: "zone_bloom", label: "ZONE BLOOM", properties: [ { 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" }, } ] }, { id: "zone_tonemapping", label: "ZONE TONEMAPPING", properties: [ { label: "Tonemapping", type: "dropdown", options: { inherit: "Inherit", disabled: "Off", enabled: "On" }, propertyID: "tonemappingMode", }, { label: "Curve", type: "dropdown", options: { rgb: "RGB", srgb: "sRGB", reinhard: "Reinhard", filmic: "Filmic" }, propertyID: "tonemapping.curve", showPropertyRule: { "tonemappingMode": "enabled" }, }, { label: "Exposure", type: "number-draggable", min: -4.0, max: 4.0, step: 0.1, decimals: 1, propertyID: "tonemapping.exposure", showPropertyRule: { "tonemappingMode": "enabled" }, } ] }, { id: "zone_ambient_occlusion", label: "ZONE AMBIENT OCCLUSION", properties: [ { label: "Ambient Occlusion", type: "dropdown", options: { inherit: "Inherit", disabled: "Off", enabled: "On" }, propertyID: "ambientOcclusionMode", }, //{ // label: "Technique", // type: "dropdown", // options: { ssao: "SSAO", hbao: "HBAO" }, // propertyID: "ambientOcclusion.technique", // showPropertyRule: { "ambientOcclusionMode": "enabled" }, //}, { label: "Jitter", type: "bool", propertyID: "ambientOcclusion.jitter", showPropertyRule: { "ambientOcclusionMode": "enabled" }, }, { label: "Resolution Level", type: "number-draggable", step: 1, decimals: 0, propertyID: "ambientOcclusion.resolutionLevel", showPropertyRule: { "ambientOcclusionMode": "enabled" }, }, { label: "Edge Sharpness", type: "number-draggable", step: 0.01, decimals: 2, propertyID: "ambientOcclusion.edgeSharpness", showPropertyRule: { "ambientOcclusionMode": "enabled" }, }, { label: "Blur Radius (pixels)", type: "number-draggable", step: 1, decimals: 0, propertyID: "ambientOcclusion.blurRadius", showPropertyRule: { "ambientOcclusionMode": "enabled" }, }, { label: "AO Radius", type: "number-draggable", step: 0.01, decimals: 2, propertyID: "ambientOcclusion.aoRadius", showPropertyRule: { "ambientOcclusionMode": "enabled" }, }, { label: "Intensity", type: "number-draggable", step: 0.01, decimals: 2, propertyID: "ambientOcclusion.aoObscuranceLevel", showPropertyRule: { "ambientOcclusionMode": "enabled" }, }, { label: "Falloff Angle", type: "number-draggable", step: 0.01, decimals: 2, propertyID: "ambientOcclusion.aoFalloffAngle", showPropertyRule: { "ambientOcclusionMode": "enabled" }, }, { label: "Sampling Amount", type: "number-draggable", step: 0.01, decimals: 2, propertyID: "ambientOcclusion.aoSamplingAmount", showPropertyRule: { "ambientOcclusionMode": "enabled" }, }, { label: "Num Spiral Turns", type: "number-draggable", step: 0.01, decimals: 2, propertyID: "ambientOcclusion.ssaoNumSpiralTurns", showPropertyRule: { "ambientOcclusionMode": "enabled" }, } ] }, { id: "zone_avatar_priority", label: "ZONE AVATAR PRIORITY", properties: [ { label: "Avatar Priority", type: "dropdown", options: { inherit: "Inherit", crowd: "Crowd", hero: "Hero" }, propertyID: "avatarPriority", } ] }, { id: "zone_audio", label: "ZONE AUDIO", properties: [ { label: "Enable Reverb", type: "bool", propertyID: "audio.reverbEnabled" }, { label: "Reverb Time", type: "number-draggable", min: 0, max: 10, step: 0.1, decimals: 2, propertyID: "audio.reverbTime", showPropertyRule: { "audio.reverbEnabled": "true" }, }, { label: "Reverb Wet Level", type: "number-draggable", min: 0, max: 100, step: 1, decimals: 1, propertyID: "audio.reverbWetLevel", showPropertyRule: { "audio.reverbEnabled": "true" }, }, { label: "Listener Zones", type: "multipleZonesSelection", propertyID: "audio.listenerZones", zonesCoefficientID: "audio.listenerAttenuationCoefficients", }, { label: "Attenuation Coef.", type: "zonesCoefficient", propertyID: "audio.listenerAttenuationCoefficients", multipleZonesSelectionID: "audio.listenerZones", } ] }, { id: "zone_normal_map_attenuation", label: "NORMAL MAP ATTENUATION", properties: [ { label: "Attenuation", type: "dropdown", options: { inherit: "Inherit", disabled: "Off", enabled: "On" }, propertyID: "normalMapAttenuationMode", }, { label: "Fade Min Distance", type: "number-draggable", min: 0, max: 16000, step: 1, decimals: 2, propertyID: "normalMapAttenuation.min", showPropertyRule: { "normalMapAttenuationMode": "enabled" }, }, { label: "Fade Max Distance", type: "number-draggable", min: 0, max: 16000, step: 1, decimals: 2, propertyID: "normalMapAttenuation.max", showPropertyRule: { "normalMapAttenuationMode": "enabled" }, } ] }, { id: "model", label: "MODEL", properties: [ { label: "Model", type: "string", placeholder: "URL", propertyID: "modelURL", }, { 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", placeholder: "URL", }, { label: "Use Original Pivot", type: "bool", propertyID: "useOriginalPivot", }, { label: "Animation", type: "string", propertyID: "animation.url", placeholder: "URL", }, { label: "Play Automatically", type: "bool", propertyID: "animation.running", }, { label: "Loop", type: "bool", propertyID: "animation.loop", }, { label: "Allow Translation", 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: "Smooth Animation", type: "bool", propertyID: "animation.smoothFrames", }, { label: "Texture", type: "textarea", propertyID: "textures", }, { label: "Original Texture", type: "textarea", propertyID: "originalTextures", readOnly: true, }, { label: "Group Culled", type: "bool", propertyID: "groupCulled", } ] }, { id: "image", label: "IMAGE", properties: [ { label: "Image", type: "string", placeholder: "URL", propertyID: "imageURL", }, { label: "Color", type: "color", propertyID: "imageColor", propertyName: "color", // actual entity property name }, { label: "Alpha", type: "number-draggable", min: 0, max: 1, step: 0.01, decimals: 2, propertyID: "imageAlpha", propertyName: "alpha", }, { label: "Emissive", type: "bool", propertyID: "emissive", }, { label: "Sub Image", type: "rect", min: 0, step: 1, subLabels: [ "x", "y", "w", "h" ], propertyID: "subImage", }, { label: "Keep Aspect Ratio", type: "bool", propertyID: "keepAspectRatio", } ] }, { id: "web", label: "WEB", properties: [ { label: "Source", type: "string", propertyID: "sourceUrl", placeholder: "URL", }, { 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: "Use Background", type: "bool", propertyID: "useBackground", }, { label: "Max FPS", type: "number-draggable", step: 1, decimals: 0, propertyID: "maxFPS", }, { label: "Input Mode", type: "dropdown", options: { touch: "Touch events", mouse: "Mouse events" }, propertyID: "inputMode", }, { label: "Wants Keyboard Focus", type: "bool", propertyID: "wantsKeyboardFocus", }, { label: "Focus Highlight", type: "bool", propertyID: "showKeyboardFocusHighlight", }, { label: "Script URL", type: "string", propertyID: "webScriptURL", propertyName: "scriptURL", // actual entity property name placeholder: "URL", }, { label: "User Agent", type: "string", propertyID: "userAgent", placeholder: "User Agent", } ] }, { id: "light", label: "LIGHT", properties: [ { label: "Light Color", type: "color", propertyID: "lightColor", propertyName: "color", // actual entity property name }, { label: "Intensity", type: "number-draggable", min: -1000, 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", label: "MATERIAL", properties: [ { label: "Material URL", type: "string", propertyID: "materialURL", placeholder: "URL", }, { label: "Material Data", type: "textarea", buttons: [ { id: "materialAssistant", label: "Assistant...", className: "secondary_blue blue", onClick: openMaterialAssistant }, { id: "clear", label: "Clear Material", className: "secondary_red red", onClick: clearMaterialData }, { id: "edit", label: "Edit as JSON", className: "secondary", onClick: newJSONMaterialEditor }, { id: "save", label: "Save Material", className: "secondary", 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", triplanar: "Triplanar mapping" }, propertyID: "materialMappingMode", }, { label: "Material Position", type: "vec2", vec2Type: "xyz", min: 0, max: 1, step: 0.005, decimals: 4, subLabels: [ "x", "y" ], propertyID: "materialMappingPos", }, { label: "Material Scale", type: "vec2", vec2Type: "xyz", step: 0.005, 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", label: "GRID", properties: [ { label: "Color", type: "color", propertyID: "gridColor", propertyName: "color", // actual entity property name }, { label: "Alpha", type: "number-draggable", min: 0, max: 1, step: 0.01, decimals: 3, propertyID: "gridAlpha", propertyName: "alpha", }, { 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", label: "PARTICLES", 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: "PARTICLES EMIT", 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", placeholder: "URL", }, { 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: "PARTICLES SIZE", 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: "PARTICLES COLOR", 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: "vec3rgb", vec3Type: "vec3rgb", min: 0, max: 255, step: 1, decimals: 0, subLabels: [ "r", "g", "b" ], propertyID: "colorSpread", }, { 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_behavior", label: "PARTICLES BEHAVIOR", 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", }, { 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: "PARTICLES CONSTRAINTS", 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: "particles_procedural", label: "PROCEDURAL PARTICLES", properties: [ { label: "Particles", type: "number-draggable", propertyID: "numParticles", min: 1, max: 1000000 }, { label: "Triangles Per Particle", type: "number-draggable", propertyID: "numTrianglesPerParticle", min: 1, max: 15 }, { label: "Update Props", type: "number-draggable", propertyID: "numUpdateProps", min: 0, max: 5 }, { label: "Transparent", type: "bool", propertyID: "particleTransparent", }, { label: "Particle Update Data", type: "textarea", buttons: [{ id: "clear", label: "Clear Update Data", className: "secondary_red red", onClick: clearParticleUpdateData }, { id: "edit", label: "Edit as JSON", className: "secondary", onClick: newJSONParticleUpdateEditor }, { id: "save", label: "Save Update Data", className: "secondary", onClick: saveParticleUpdateData }], propertyID: "particleUpdateData", }, { label: "Particle Render Data", type: "textarea", buttons: [{ id: "clear", label: "Clear Render Data", className: "secondary_red red", onClick: clearParticleRenderData }, { id: "edit", label: "Edit as JSON", className: "secondary", onClick: newJSONParticleRenderEditor }, { id: "save", label: "Save Render Data", className: "secondary", onClick: saveParticleRenderData }], propertyID: "particleRenderData", } ] }, { id: "polyvox", label: "POLYVOX", properties: [ { label: "Volume Size", type: "vec3", vec3Type: "xyz", step: 1.0, decimals: 0, subLabels: [ "x", "y", "z" ], unit: "", propertyID: "voxelVolumeSize", }, { label: "Surface Style", type: "dropdown", options: { 0: "Marching cubes", 1: "Cubic", 2: "Edged cubic", 3: "Edged marching cubes" }, propertyID: "voxelSurfaceStyle", propertyName: "voxelSurfaceStyle", }, { label: "Texture preset", type: "dropdown", options: { 0 : "None", 1 : "Grass + ground", 2 : "Bricks", 3 : "Stone", 4: "Concrete", 5 : "Rock"}, propertyID: "polyVoxPreset", onDropdownChange: createPolyVoxPresetChangedFunction, skipPropertyUpdate: true, }, { label: "X Texture URL", type: "string", propertyID: "xTextureURL", propertyName: "xTextureURL", placeholder: "URL", }, { label: "Y Texture URL", type: "string", propertyID: "yTextureURL", propertyName: "yTextureURL", placeholder: "URL", }, { label: "Z Texture URL", type: "string", propertyID: "zTextureURL", propertyName: "zTextureURL", placeholder: "URL", }, ] }, { id: "sound", label: "SOUND", properties: [ { label: "Sound", type: "string", propertyID: "soundURL", placeholder: "URL", }, { label: "Playing", type: "bool", propertyID: "playing", }, { label: "Loop", type: "bool", propertyID: "loop", }, { label: "Volume", type: "number-draggable", min: 0, max: 1, step: 0.01, decimals: 2, propertyID: "volume", }, { label: "Positional", type: "bool", propertyID: "positional", }, { label: "Pitch", type: "number-draggable", min: 0.0625, max: 16, step: 0.1, decimals: 2, propertyID: "pitch", }, { label: "Time Offset", type: "number-draggable", step: 0.1, propertyID: "timeOffset", }, { label: "Local Only", type: "bool", propertyID: "localOnly", } ] }, { id: "canvas", label: "CANVAS", properties: [ { label: "Width", type: "number-draggable", propertyID: "width", }, { label: "Height", type: "number-draggable", propertyID: "height", }, { label: "Unlit", type: "bool", propertyID: "unlit", }, { label: "Transparent", type: "bool", propertyID: "transparent", } ] }, { id: "empty", label: "EMPTY", properties: [] }, { id: "script", label: "SCRIPT", properties: [ { label: "Script", type: "string", propertyID: "scriptURL", placeholder: "URL", }, { label: "Enabled", type: "bool", propertyID: "enabled", }, ] }, { 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, }, { type: "buttons", buttons: [ { id: "copyPosition", label: "Copy Position", className: "secondary", onClick: copyPositionProperty }, { id: "pastePosition", label: "Paste Position", className: "secondary", onClick: pastePositionProperty } ], propertyID: "copyPastePosition" }, { 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, }, { type: "buttons", buttons: [ { id: "copyRotation", label: "Copy Rotation", className: "secondary", onClick: copyRotationProperty }, { id: "pasteRotation", label: "Paste Rotation", className: "secondary", onClick: pasteRotationProperty }, { id: "setRotationToZero", label: "Reset Rotation", className: "secondary_red red", onClick: setRotationToZeroProperty }], propertyID: "copyPasteRotation" }, { 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, }, { type: "buttons", buttons: [ { id: "copyDimensions", label: "Copy Dimensions", className: "secondary", onClick: copyDimensionsProperty }, { id: "pasteDimensions", label: "Paste Dimensions", className: "secondary", onClick: pasteDimensionsProperty } ], propertyID: "copyPasteDimensions" }, { 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: "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: "Cast Shadows", type: "bool", propertyID: "canCastShadow", }, { label: "Link", type: "string", propertyID: "href", placeholder: "URL", }, { label: "Ignore Pick Intersection", type: "bool", propertyID: "ignorePickIntersection", }, { label: "Lifetime", type: "number", unit: "s", propertyID: "lifetime", } ] }, { id: "grabAndEquip", label: "GRAB AND EQUIP", properties: [ { label: "Grabbable", type: "bool", propertyID: "grab.grabbable", }, { label: "Follow Controller", type: "bool", propertyID: "grab.grabFollowsController", showPropertyRule: { "grab.grabbable": "true" }, }, { label: "Kinematic Grab", type: "bool", propertyID: "grab.grabKinematic", showPropertyRule: { "grab.grabbable": "true" }, }, { label: "Delegate To Parent", type: "bool", propertyID: "grab.grabDelegateToParent", showPropertyRule: { "grab.grabbable": "true" }, }, { label: "Triggerable", type: "bool", propertyID: "grab.triggerable", }, { label: "Equippable", type: "bool", propertyID: "grab.equippable", }, { label: "Left Position", type: "vec3", vec3Type: "xyz", subLabels: [ "x", "y", "z" ], step: 0.0025, decimals: 4, unit: "m", propertyID: "grab.equippableLeftPosition", showPropertyRule: { "grab.equippable": "true" }, }, { label: "Left Rotation", type: "vec3", vec3Type: "pyr", step: 0.1, decimals: 4, subLabels: [ "x", "y", "z" ], unit: "deg", propertyID: "grab.equippableLeftRotation", showPropertyRule: { "grab.equippable": "true" }, }, { label: "Right Position", type: "vec3", vec3Type: "xyz", subLabels: [ "x", "y", "z" ], step: 0.0025, decimals: 4, unit: "m", propertyID: "grab.equippableRightPosition", showPropertyRule: { "grab.equippable": "true" }, }, { label: "Right Rotation", type: "vec3", vec3Type: "pyr", step: 0.1, decimals: 4, subLabels: [ "x", "y", "z" ], unit: "deg", propertyID: "grab.equippableRightRotation", showPropertyRule: { "grab.equippable": "true" }, }, { label: "Indicator Model URL", type: "string", propertyID: "grab.equippableIndicatorURL", placeholder: "URL", showPropertyRule: { "grab.equippable": "true" }, }, { label: "Indicator Scale", type: "vec3", vec3Type: "xyz", subLabels: [ "x", "y", "z" ], step: 0.0025, decimals: 4, unit: "scale", propertyID: "grab.equippableIndicatorScale", showPropertyRule: { "grab.equippable": "true" }, }, { label: "Indicator Offset", type: "vec3", vec3Type: "xyz", subLabels: [ "x", "y", "z" ], step: 0.005, decimals: 4, unit: "m", propertyID: "grab.equippableIndicatorOffset", showPropertyRule: { "grab.equippable": "true" }, }, ] }, { id: "scripts", label: "SCRIPTS", properties: [ { label: "Script", type: "code", buttons: [ { id: "reload", label: "F", className: "glyph", onClick: reloadScripts } ], propertyID: "script", placeholder: "URL", }, { label: "Server Script", type: "code", 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: "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" }, }, { 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: 6, step: 1, 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/s2", propertyID: "gravity", } ] }, { id: "fading", label: "FADING", properties: [ { label: "Fade In", type: "dropdown", options: { inherit: "Inherit", disabled: "Off", enabled: "On" }, propertyName: "fadeInMode", propertyID: "entityFadeInMode" }, { label: "Duration", type: "number-draggable", min: 0, max: 2, step: 0.001, decimals: 3, propertyName: "fadeIn.duration", propertyID: "entityFadeInDuration", showPropertyRule: { "entityFadeInMode": "enabled" }, }, { label: "Noise Size", type: "vec3", vec3Type: "xyz", subLabels: [ "x", "y", "z" ], step: 0.1, decimals: 4, propertyName: "fadeIn.noiseSize", propertyID: "entityFadeInNoiseSize", showPropertyRule: { "entityFadeInMode": "enabled" }, }, { label: "Noise Level", type: "number-draggable", min: 0, max: 5, step: 0.001, decimals: 3, propertyName: "fadeIn.noiseLevel", propertyID: "entityFadeInNoiseLevel", showPropertyRule: { "entityFadeInMode": "enabled" }, }, { label: "Noise Speed", type: "vec3", vec3Type: "xyz", subLabels: [ "x", "y", "z" ], step: 0.1, decimals: 4, propertyName: "fadeIn.noiseSpeed", propertyID: "entityFadeInNoiseSpeed", showPropertyRule: { "entityFadeInMode": "enabled" }, }, { label: "Base Size", type: "vec3", vec3Type: "xyz", subLabels: [ "x", "y", "z" ], step: 0.1, decimals: 4, propertyName: "fadeIn.baseSize", propertyID: "entityFadeInBaseSize", showPropertyRule: { "entityFadeInMode": "enabled" }, }, { label: "Base Level", type: "number-draggable", min: 0, max: 5, step: 0.001, decimals: 3, propertyName: "fadeIn.baseLevel", propertyID: "entityFadeInBaseLevel", showPropertyRule: { "entityFadeInMode": "enabled" }, }, { label: "Edge Width", type: "number-draggable", min: 0, max: 1, step: 0.001, decimals: 3, propertyName: "fadeIn.edgeWidth", propertyID: "entityFadeInEdgeWidth", showPropertyRule: { "entityFadeInMode": "enabled" }, }, { label: "Inner Color", type: "color", propertyName: "fadeIn.edgeInnerColor", propertyID: "entityFadeInEdgeInnerColor", showPropertyRule: { "entityFadeInMode": "enabled" }, }, { label: "Inner Alpha", type: "number-draggable", min: 0, max: 1, step: 0.01, decimals: 2, propertyName: "fadeIn.edgeInnerAlpha", propertyID: "entityFadeInEdgeInnerAlpha", showPropertyRule: { "entityFadeInMode": "enabled" }, }, { label: "Outer Color", type: "color", propertyName: "fadeIn.edgeOuterColor", propertyID: "entityFadeInEdgeOuterColor", showPropertyRule: { "entityFadeInMode": "enabled" }, }, { label: "Outer Alpha", type: "number-draggable", min: 0, max: 1, step: 0.01, decimals: 2, propertyName: "fadeIn.edgeOuterAlpha", propertyID: "entityFadeInEdgeOuterAlpha", showPropertyRule: { "entityFadeInMode": "enabled" }, }, { label: "Inverted", type: "bool", propertyName: "fadeIn.inverted", propertyID: "entityFadeInInverted", showPropertyRule: { "entityFadeInMode": "enabled" }, }, { label: "Timing", type: "dropdown", options: { linear: "Linear", easeIn: "Ease In", easeOut: "Ease Out", easeInOut: "Ease In/Out" }, propertyName: "fadeIn.timing", propertyID: "entityFadeInTiming", showPropertyRule: { "entityFadeInMode": "enabled" }, }, { label: "Fade Out", type: "dropdown", options: { inherit: "Inherit", disabled: "Off", enabled: "On" }, propertyName: "fadeOutMode", propertyID: "entityFadeOutMode" }, { label: "Duration", type: "number-draggable", min: 0, max: 2, step: 0.001, decimals: 3, propertyName: "fadeOut.duration", propertyID: "entityFadeOutDuration", showPropertyRule: { "entityFadeOutMode": "enabled" }, }, { label: "Noise Size", type: "vec3", vec3Type: "xyz", subLabels: ["x", "y", "z"], step: 0.1, decimals: 4, propertyName: "fadeOut.noiseSize", propertyID: "entityFadeOutNoiseSize", showPropertyRule: { "entityFadeOutMode": "enabled" }, }, { label: "Noise Level", type: "number-draggable", min: 0, max: 5, step: 0.001, decimals: 3, propertyName: "fadeOut.noiseLevel", propertyID: "entityFadeOutNoiseLevel", showPropertyRule: { "entityFadeOutMode": "enabled" }, }, { label: "Noise Speed", type: "vec3", vec3Type: "xyz", subLabels: ["x", "y", "z"], step: 0.1, decimals: 4, propertyName: "fadeOut.noiseSpeed", propertyID: "entityFadeOutNoiseSpeed", showPropertyRule: { "entityFadeOutMode": "enabled" }, }, { label: "Base Size", type: "vec3", vec3Type: "xyz", subLabels: ["x", "y", "z"], step: 0.1, decimals: 4, propertyName: "fadeOut.baseSize", propertyID: "entityFadeOutBaseSize", showPropertyRule: { "entityFadeOutMode": "enabled" }, }, { label: "Base Level", type: "number-draggable", min: 0, max: 5, step: 0.001, decimals: 3, propertyName: "fadeOut.baseLevel", propertyID: "entityFadeOutBaseLevel", showPropertyRule: { "entityFadeOutMode": "enabled" }, }, { label: "Edge Width", type: "number-draggable", min: 0, max: 1, step: 0.001, decimals: 3, propertyName: "fadeOut.edgeWidth", propertyID: "entityFadeOutEdgeWidth", showPropertyRule: { "entityFadeOutMode": "enabled" }, }, { label: "Inner Color", type: "color", propertyName: "fadeOut.edgeInnerColor", propertyID: "entityFadeOutEdgeInnerColor", showPropertyRule: { "entityFadeOutMode": "enabled" }, }, { label: "Inner Alpha", type: "number-draggable", min: 0, max: 1, step: 0.01, decimals: 2, propertyName: "fadeOut.edgeInnerAlpha", propertyID: "entityFadeOutEdgeInnerAlpha", showPropertyRule: { "entityFadeOutMode": "enabled" }, }, { label: "Outer Color", type: "color", propertyName: "fadeOut.edgeOuterColor", propertyID: "entityFadeOutEdgeOuterColor", showPropertyRule: { "entityFadeOutMode": "enabled" }, }, { label: "Outer Alpha", type: "number-draggable", min: 0, max: 1, step: 0.01, decimals: 2, propertyName: "fadeOut.edgeOuterAlpha", propertyID: "entityFadeOutEdgeOuterAlpha", showPropertyRule: { "entityFadeOutMode": "enabled" }, }, { label: "Inverted", type: "bool", propertyName: "fadeOut.inverted", propertyID: "entityFadeOutInverted", showPropertyRule: { "entityFadeOutMode": "enabled" }, }, { label: "Timing", type: "dropdown", options: { linear: "Linear", easeIn: "Ease In", easeOut: "Ease Out", easeInOut: "Ease In/Out" }, propertyName: "fadeOut.timing", propertyID: "entityFadeOutTiming", showPropertyRule: { "entityFadeOutMode": "enabled" }, }, ] }, { id: "zone_fading", label: "FADING", properties: [ { label: "Fade In", type: "dropdown", options: { inherit: "Inherit", disabled: "Off", enabled: "On" }, propertyID: "fadeInMode", }, { label: "Duration", type: "number-draggable", min: 0, max: 2, step: 0.001, decimals: 3, propertyID: "fadeIn.duration", showPropertyRule: { "fadeInMode": "enabled" }, }, { label: "Noise Size", type: "vec3", vec3Type: "xyz", subLabels: ["x", "y", "z"], step: 0.1, decimals: 4, propertyID: "fadeIn.noiseSize", showPropertyRule: { "fadeInMode": "enabled" }, }, { label: "Noise Level", type: "number-draggable", min: 0, max: 5, step: 0.001, decimals: 3, propertyID: "fadeIn.noiseLevel", showPropertyRule: { "fadeInMode": "enabled" }, }, { label: "Noise Speed", type: "vec3", vec3Type: "xyz", subLabels: ["x", "y", "z"], step: 0.1, decimals: 4, propertyID: "fadeIn.noiseSpeed", showPropertyRule: { "fadeInMode": "enabled" }, }, { label: "Base Size", type: "vec3", vec3Type: "xyz", subLabels: ["x", "y", "z"], step: 0.1, decimals: 4, propertyID: "fadeIn.baseSize", showPropertyRule: { "fadeInMode": "enabled" }, }, { label: "Base Level", type: "number-draggable", min: 0, max: 5, step: 0.001, decimals: 3, propertyID: "fadeIn.baseLevel", showPropertyRule: { "fadeInMode": "enabled" }, }, { label: "Edge Width", type: "number-draggable", min: 0, max: 1, step: 0.001, decimals: 3, propertyID: "fadeIn.edgeWidth", showPropertyRule: { "fadeInMode": "enabled" }, }, { label: "Inner Color", type: "color", propertyID: "fadeIn.edgeInnerColor", showPropertyRule: { "fadeInMode": "enabled" }, }, { label: "Inner Alpha", type: "number-draggable", min: 0, max: 1, step: 0.01, decimals: 2, propertyID: "fadeIn.edgeInnerAlpha", showPropertyRule: { "fadeInMode": "enabled" }, }, { label: "Outer Color", type: "color", propertyID: "fadeIn.edgeOuterColor", showPropertyRule: { "fadeInMode": "enabled" }, }, { label: "Outer Alpha", type: "number-draggable", min: 0, max: 1, step: 0.01, decimals: 2, propertyID: "fadeIn.edgeOuterAlpha", showPropertyRule: { "fadeInMode": "enabled" }, }, { label: "Inverted", type: "bool", propertyID: "fadeIn.inverted", showPropertyRule: { "fadeInMode": "enabled" }, }, { label: "Timing", type: "dropdown", options: { linear: "Linear", easeIn: "Ease In", easeOut: "Ease Out", easeInOut: "Ease In/Out" }, propertyID: "fadeIn.timing", showPropertyRule: { "fadeInMode": "enabled" }, }, { label: "Fade Out", type: "dropdown", options: { inherit: "Inherit", disabled: "Off", enabled: "On" }, propertyID: "fadeOutMode", }, { label: "Duration", type: "number-draggable", min: 0, max: 2, step: 0.001, decimals: 3, propertyID: "fadeOut.duration", showPropertyRule: { "fadeOutMode": "enabled" }, }, { label: "Noise Size", type: "vec3", vec3Type: "xyz", subLabels: ["x", "y", "z"], step: 0.1, decimals: 4, propertyID: "fadeOut.noiseSize", showPropertyRule: { "fadeOutMode": "enabled" }, }, { label: "Noise Level", type: "number-draggable", min: 0, max: 5, step: 0.001, decimals: 3, propertyID: "fadeOut.noiseLevel", showPropertyRule: { "fadeOutMode": "enabled" }, }, { label: "Noise Speed", type: "vec3", vec3Type: "xyz", subLabels: ["x", "y", "z"], step: 0.1, decimals: 4, propertyID: "fadeOut.noiseSpeed", showPropertyRule: { "fadeOutMode": "enabled" }, }, { label: "Base Size", type: "vec3", vec3Type: "xyz", subLabels: ["x", "y", "z"], step: 0.1, decimals: 4, propertyID: "fadeOut.baseSize", showPropertyRule: { "fadeOutMode": "enabled" }, }, { label: "Base Level", type: "number-draggable", min: 0, max: 5, step: 0.001, decimals: 3, propertyID: "fadeOut.baseLevel", showPropertyRule: { "fadeOutMode": "enabled" }, }, { label: "Edge Width", type: "number-draggable", min: 0, max: 1, step: 0.001, decimals: 3, propertyID: "fadeOut.edgeWidth", showPropertyRule: { "fadeOutMode": "enabled" }, }, { label: "Inner Color", type: "color", propertyID: "fadeOut.edgeInnerColor", showPropertyRule: { "fadeOutMode": "enabled" }, }, { label: "Inner Alpha", type: "number-draggable", min: 0, max: 1, step: 0.01, decimals: 2, propertyID: "fadeOut.edgeInnerAlpha", showPropertyRule: { "fadeOutMode": "enabled" }, }, { label: "Outer Color", type: "color", propertyID: "fadeOut.edgeOuterColor", showPropertyRule: { "fadeOutMode": "enabled" }, }, { label: "Outer Alpha", type: "number-draggable", min: 0, max: 1, step: 0.01, decimals: 2, propertyID: "fadeOut.edgeOuterAlpha", showPropertyRule: { "fadeOutMode": "enabled" }, }, { label: "Inverted", type: "bool", propertyID: "fadeOut.inverted", showPropertyRule: { "fadeOutMode": "enabled" }, }, { label: "Timing", type: "dropdown", options: { linear: "Linear", easeIn: "Ease In", easeOut: "Ease Out", easeInOut: "Ease In/Out" }, propertyID: "fadeOut.timing", showPropertyRule: { "fadeOutMode": "enabled" }, }, { label: "Avatar Fade In", type: "dropdown", options: { inherit: "Inherit", disabled: "Off", enabled: "On" }, propertyID: "avatarFadeInMode", }, { label: "Duration", type: "number-draggable", min: 0, max: 2, step: 0.001, decimals: 3, propertyID: "avatarFadeIn.duration", showPropertyRule: { "avatarFadeInMode": "enabled" }, }, { label: "Noise Size", type: "vec3", vec3Type: "xyz", subLabels: ["x", "y", "z"], step: 0.1, decimals: 4, propertyID: "avatarFadeIn.noiseSize", showPropertyRule: { "avatarFadeInMode": "enabled" }, }, { label: "Noise Level", type: "number-draggable", min: 0, max: 5, step: 0.001, decimals: 3, propertyID: "avatarFadeIn.noiseLevel", showPropertyRule: { "avatarFadeInMode": "enabled" }, }, { label: "Noise Speed", type: "vec3", vec3Type: "xyz", subLabels: ["x", "y", "z"], step: 0.1, decimals: 4, propertyID: "avatarFadeIn.noiseSpeed", showPropertyRule: { "avatarFadeInMode": "enabled" }, }, { label: "Base Size", type: "vec3", vec3Type: "xyz", subLabels: ["x", "y", "z"], step: 0.1, decimals: 4, propertyID: "avatarFadeIn.baseSize", showPropertyRule: { "avatarFadeInMode": "enabled" }, }, { label: "Base Level", type: "number-draggable", min: 0, max: 5, step: 0.001, decimals: 3, propertyID: "avatarFadeIn.baseLevel", showPropertyRule: { "avatarFadeInMode": "enabled" }, }, { label: "Edge Width", type: "number-draggable", min: 0, max: 1, step: 0.001, decimals: 3, propertyID: "avatarFadeIn.edgeWidth", showPropertyRule: { "avatarFadeInMode": "enabled" }, }, { label: "Inner Color", type: "color", propertyID: "avatarFadeIn.edgeInnerColor", showPropertyRule: { "avatarFadeInMode": "enabled" }, }, { label: "Inner Alpha", type: "number-draggable", min: 0, max: 1, step: 0.01, decimals: 2, propertyID: "avatarFadeIn.edgeInnerAlpha", showPropertyRule: { "avatarFadeInMode": "enabled" }, }, { label: "Outer Color", type: "color", propertyID: "avatarFadeIn.edgeOuterColor", showPropertyRule: { "avatarFadeInMode": "enabled" }, }, { label: "Outer Alpha", type: "number-draggable", min: 0, max: 1, step: 0.01, decimals: 2, propertyID: "avatarFadeIn.edgeOuterAlpha", showPropertyRule: { "avatarFadeInMode": "enabled" }, }, { label: "Inverted", type: "bool", propertyID: "avatarFadeIn.inverted", showPropertyRule: { "avatarFadeInMode": "enabled" }, }, { label: "Timing", type: "dropdown", options: { linear: "Linear", easeIn: "Ease In", easeOut: "Ease Out", easeInOut: "Ease In/Out" }, propertyID: "avatarFadeIn.timing", showPropertyRule: { "avatarFadeInMode": "enabled" }, }, { label: "Avatar Fade Out", type: "dropdown", options: { inherit: "Inherit", disabled: "Off", enabled: "On" }, propertyID: "avatarFadeOutMode", }, { label: "Duration", type: "number-draggable", min: 0, max: 2, step: 0.001, decimals: 3, propertyID: "avatarFadeOut.duration", showPropertyRule: { "avatarFadeOutMode": "enabled" }, }, { label: "Noise Size", type: "vec3", vec3Type: "xyz", subLabels: ["x", "y", "z"], step: 0.1, decimals: 4, propertyID: "avatarFadeOut.noiseSize", showPropertyRule: { "avatarFadeOutMode": "enabled" }, }, { label: "Noise Level", type: "number-draggable", min: 0, max: 5, step: 0.001, decimals: 3, propertyID: "avatarFadeOut.noiseLevel", showPropertyRule: { "avatarFadeOutMode": "enabled" }, }, { label: "Noise Speed", type: "vec3", vec3Type: "xyz", subLabels: ["x", "y", "z"], step: 0.1, decimals: 4, propertyID: "avatarFadeOut.noiseSpeed", showPropertyRule: { "avatarFadeOutMode": "enabled" }, }, { label: "Base Size", type: "vec3", vec3Type: "xyz", subLabels: ["x", "y", "z"], step: 0.1, decimals: 4, propertyID: "avatarFadeOut.baseSize", showPropertyRule: { "avatarFadeOutMode": "enabled" }, }, { label: "Base Level", type: "number-draggable", min: 0, max: 5, step: 0.001, decimals: 3, propertyID: "avatarFadeOut.baseLevel", showPropertyRule: { "avatarFadeOutMode": "enabled" }, }, { label: "Edge Width", type: "number-draggable", min: 0, max: 1, step: 0.001, decimals: 3, propertyID: "avatarFadeOut.edgeWidth", showPropertyRule: { "avatarFadeOutMode": "enabled" }, }, { label: "Inner Color", type: "color", propertyID: "avatarFadeOut.edgeInnerColor", showPropertyRule: { "avatarFadeOutMode": "enabled" }, }, { label: "Inner Alpha", type: "number-draggable", min: 0, max: 1, step: 0.01, decimals: 2, propertyID: "avatarFadeOut.edgeInnerAlpha", showPropertyRule: { "avatarFadeOutMode": "enabled" }, }, { label: "Outer Color", type: "color", propertyID: "avatarFadeOut.edgeOuterColor", showPropertyRule: { "avatarFadeOutMode": "enabled" }, }, { label: "Outer Alpha", type: "number-draggable", min: 0, max: 1, step: 0.01, decimals: 2, propertyID: "avatarFadeOut.edgeOuterAlpha", showPropertyRule: { "avatarFadeOutMode": "enabled" }, }, { label: "Inverted", type: "bool", propertyID: "avatarFadeOut.inverted", showPropertyRule: { "avatarFadeOutMode": "enabled" }, }, { label: "Timing", type: "dropdown", options: { linear: "Linear", easeIn: "Ease In", easeOut: "Ease Out", easeInOut: "Ease In/Out" }, propertyID: "avatarFadeOut.timing", showPropertyRule: { "avatarFadeOutMode": "enabled" }, }, ] }, { id: "children", label: "CHILD ENTITIES", properties: [ { label: "Children", type: "childList", propertyID: "children", } ] }, ]; const GROUPS_PER_TYPE = { None: [ 'base', 'spatial', 'behavior', 'grabAndEquip', 'scripts', 'collision', 'physics', 'children' ], Shape: [ 'base', 'shape', 'spatial', 'behavior', 'grabAndEquip', 'scripts', 'collision', 'physics', 'fading', 'children' ], Text: [ 'base', 'text', 'spatial', 'behavior', 'grabAndEquip', 'scripts', 'collision', 'physics', 'fading', 'children' ], Zone: [ 'base', 'zone', 'zone_key_light', 'zone_skybox', 'zone_ambient_light', 'zone_haze', 'zone_bloom', 'zone_tonemapping', 'zone_ambient_occlusion', 'zone_avatar_priority', 'zone_audio', 'zone_normal_map_attenuation', 'spatial', 'behavior', 'grabAndEquip', 'scripts', 'physics', 'zone_fading', 'children' ], Model: [ 'base', 'model', 'spatial', 'behavior', 'grabAndEquip', 'scripts', 'collision', 'physics', 'fading', 'children' ], Image: [ 'base', 'image', 'spatial', 'behavior', 'grabAndEquip', 'scripts', 'collision', 'physics', 'fading', 'children' ], Web: [ 'base', 'web', 'spatial', 'behavior', 'grabAndEquip', 'scripts', 'collision', 'physics', 'fading', 'children' ], Light: ['base', 'light', 'spatial', 'behavior', 'grabAndEquip', 'scripts', 'collision', 'physics', 'fading', 'children' ], Material: [ 'base', 'material', 'spatial', 'behavior', 'grabAndEquip', 'scripts', 'physics', 'children' ], ParticleEffect: [ 'base', 'particles', 'particles_emit', 'particles_size', 'particles_color', 'particles_behavior', 'particles_constraints', 'spatial', 'behavior', 'grabAndEquip', 'scripts', 'physics', 'children' ], ProceduralParticleEffect: [ 'base', 'particles_procedural', 'spatial', 'behavior', 'grabAndEquip', 'scripts', 'physics', 'children' ], PolyLine: [ 'base', 'spatial', 'behavior', 'grabAndEquip', 'scripts', 'collision', 'physics', 'children' ], PolyVox: [ 'base', 'polyvox', 'spatial', 'behavior', 'grabAndEquip', 'scripts', 'collision', 'physics', 'fading', 'children' ], Grid: [ 'base', 'grid', 'spatial', 'behavior', 'grabAndEquip', 'scripts', 'physics', 'children' ], Sound: [ 'base', 'sound', 'spatial', 'behavior', 'grabAndEquip', 'scripts', 'physics', 'children' ], Canvas: [ 'base', 'canvas', 'spatial', 'behavior', 'grabAndEquip', 'scripts', 'collision', 'physics', 'children' ], Empty: [ 'base', 'spatial', 'scripts', 'children' ], Script: [ 'base', 'script', 'spatial', 'behavior', 'grabAndEquip', 'scripts', 'physics', 'children' ], Multiple: [ 'base', 'spatial', 'behavior', 'grabAndEquip', 'scripts', 'collision', 'physics', 'fading', 'children' ], }; 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; let zonesList = []; let canViewAssetURLs = false; let maSelectedId; let skyboxColorForCopy; 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 'code': case 'texture': return property.elInput; case 'multipleZonesSelection': return property.elInput; case 'zonesCoefficient': return property.elInput; case 'arrayOfStrings': 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 'vec3rgb': 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"); enableAllMultipleZoneSelector(); } function disableProperties() { disableChildren(document.getElementById("properties-list"), ENABLE_DISABLE_SELECTOR); disableChildren(document, ".colpick"); for (let pickKey in colorPickers) { colorPickers[pickKey].colpickHide(); } disableAllMultipleZoneSelector(); } function showPropertyElement(propertyID, show) { setPropertyVisibility(properties[propertyID], show); } function setPropertyVisibility(property, visible) { property.elContainer.style.display = visible ? null : "none"; } function setCopyPastePositionAndRotationAvailability (selectionLength, islocked) { if (selectionLength === 1) { $('#property-copyPastePosition-button-copyPosition').attr('disabled', false); $('#property-copyPasteRotation-button-copyRotation').attr('disabled', false); $('#property-copyPasteDimensions-button-copyDimensions').attr('disabled', false); } else { $('#property-copyPastePosition-button-copyPosition').attr('disabled', true); $('#property-copyPasteRotation-button-copyRotation').attr('disabled', true); $('#property-copyPasteDimensions-button-copyDimensions').attr('disabled', true); } if (selectionLength > 0 && !islocked) { $('#property-copyPastePosition-button-pastePosition').attr('disabled', false); $('#property-copyPasteRotation-button-pasteRotation').attr('disabled', false); $('#property-copyPasteDimensions-button-pasteDimensions').attr('disabled', false); if (selectionLength === 1) { $('#property-copyPasteRotation-button-setRotationToZero').attr('disabled', false); } else { $('#property-copyPasteRotation-button-setRotationToZero').attr('disabled', true); } } else { $('#property-copyPastePosition-button-pastePosition').attr('disabled', true); $('#property-copyPasteRotation-button-pasteRotation').attr('disabled', true); $('#property-copyPasteRotation-button-setRotationToZero').attr('disabled', true); $('#property-copyPasteDimensions-button-pasteDimensions').attr('disabled', true); } } 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 'vec3rgb': { 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': case 'code': { property.elInput.classList.remove('multi-diff'); property.elInput.value = ""; setTextareaScrolling(property.elInput); break; } case 'multipleZonesSelection': { property.elInput.classList.remove('multi-diff'); property.elInput.value = "[]"; setZonesSelectionData(property.elInput, false); break; } case 'arrayOfStrings': { property.elInput.classList.remove('multi-diff'); property.elInput.value = "[]"; setArrayOfStringsUi(property.elInput.id, false); break; } case 'zonesCoefficient': { property.elInput.classList.remove('multi-diff'); property.elInput.value = "[]"; setZonesCoefficientUi(property.elInput.id, false); break; } case 'childList': { setChildListData(property.elInput, undefined, ""); 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"]); showOnTheSamePage(["Shape"]); return; } if (type === "None") { showGroupsForTypes(["None"]); return; } showGroupsForTypes([type]); showOnTheSamePage([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 = "none"; if (types !== "None") { document.getElementById("tab-" + groupKey).style.display = "block"; } else { document.getElementById("tab-" + groupKey).style.display = "none"; } } else { elGroup.style.display = "none"; document.getElementById("tab-" + groupKey).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', 'vec3rgb']; 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; case 'vec3rgb': 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() { // If we don't have canViewAssetURLs permissions, ignore clearing URLs if (!canViewAssetURLs && property.data.placeholder === "URL" && this.value === "") { return; } 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(` `); 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(``)); 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(` `); 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 createVec3rgbProperty(property, elProperty) { let propertyData = property.data; elProperty.className = propertyData.vec3Type + " fstuple"; let elNumberR = createTupleNumberInput(property, propertyData.subLabels[VECTOR_ELEMENTS.X_NUMBER]); let elNumberG = createTupleNumberInput(property, propertyData.subLabels[VECTOR_ELEMENTS.Y_NUMBER]); let elNumberB = createTupleNumberInput(property, propertyData.subLabels[VECTOR_ELEMENTS.Z_NUMBER]); elProperty.appendChild(elNumberR.elDiv); elProperty.appendChild(elNumberG.elDiv); elProperty.appendChild(elNumberB.elDiv); elNumberR.setValueChangeFunction(createEmitNumberPropertyComponentUpdateFunction(property, 'red')); elNumberG.setValueChangeFunction(createEmitNumberPropertyComponentUpdateFunction(property, 'green')); elNumberB.setValueChangeFunction(createEmitNumberPropertyComponentUpdateFunction(property, 'blue')); elNumberR.setMultiDiffStepFunction(createMultiDiffStepFunction(property, 'red')); elNumberG.setMultiDiffStepFunction(createMultiDiffStepFunction(property, 'green')); elNumberB.setMultiDiffStepFunction(createMultiDiffStepFunction(property, 'blue')); let elResult = []; elResult[VECTOR_ELEMENTS.X_NUMBER] = elNumberR; elResult[VECTOR_ELEMENTS.Y_NUMBER] = elNumberG; elResult[VECTOR_ELEMENTS.Z_NUMBER] = elNumberB; 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; if (property.elNumberX) { property.elNumberX.updateMinMax(min, max); property.elNumberY.updateMinMax(min, max); if (property.elNumberZ) { property.elNumberZ.updateMinMax(min, max); } } else if (property.elNumberR) { property.elNumberR.updateMinMax(min, max); property.elNumberG.updateMinMax(min, max); if (property.elNumberB) { property.elNumberB.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 createCodeProperty(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)); elInput.addEventListener('keydown', function(event) { if (event.key === 'Tab') { event.preventDefault(); const prevStart = this.selectionStart + 1; this.value = this.value.substring(0, this.selectionStart) + "\t" + this.value.substring(this.selectionEnd); this.selectionStart = prevStart; this.selectionEnd = prevStart; } else if (event.key === 'Escape') { event.preventDefault(); this.blur(); } }); 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:/" || url.slice(0, 9).toLowerCase() === "file:///~") { 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 'vec3rgb': { let elVec3 = createVec3rgbProperty(property, elProperty); property.elNumberR = elVec3[VECTOR_ELEMENTS.X_NUMBER]; property.elNumberG = elVec3[VECTOR_ELEMENTS.Y_NUMBER]; property.elNumberB = elVec3[VECTOR_ELEMENTS.Z_NUMBER]; break; } case 'dropdown': { property.elInput = createDropdownProperty(property, propertyID, elProperty); break; } case 'textarea': { property.elInput = createTextareaProperty(property, elProperty); break; } case 'code': { property.elInput = createCodeProperty(property, elProperty); break; } case 'multipleZonesSelection': { property.elInput = createZonesSelection(property, elProperty); break; } case 'arrayOfStrings': { property.elInput = createArrayOfStrings(property, elProperty); break; } case 'zonesCoefficient': { property.elInput = createZonesCoefficient(property, elProperty); break; } case 'childList': { property.elInput = createChildList(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(); } } function createPolyVoxPresetChangedFunction(property) { return function() { property.elInput.classList.remove('multi-diff'); var xTextureURL = ""; var yTextureURL = ""; var zTextureURL = ""; switch (parseInt(this.value)) { // Clear texture entries case 0: xTextureURL = ""; yTextureURL = ""; zTextureURL = ""; break; // Grass + ground case 1: xTextureURL = "qrc:///serverless/Textures/ground_5-2K/2K-ground_5-diffuse.jpg"; yTextureURL = "qrc:///serverless/Textures/ground_grass_gen_05.png"; zTextureURL = "qrc:///serverless/Textures/ground_5-2K/2K-ground_5-diffuse.jpg"; break; // Bricks case 2: xTextureURL = "qrc:///serverless/Textures/2K-wall_stone_2-diffuse_l.jpg"; yTextureURL = "qrc:///serverless/Textures/2K-stone_floor_3-diffuse_l.jpg"; zTextureURL = "qrc:///serverless/Textures/2K-wall_stone_2-diffuse_l.jpg"; break; // Stone case 3: xTextureURL = "qrc:///serverless/Textures/wall_l.png"; yTextureURL = "qrc:///serverless/Textures/floor_l.png"; zTextureURL = "qrc:///serverless/Textures/wall_l.png"; break; // Concrete case 4: xTextureURL = "qrc:///serverless/Textures/concrete_12-2K/2K-concrete_12-diffuse.jpg"; yTextureURL = "qrc:///serverless/Textures/concrete_12-2K/2K-concrete_12-diffuse.jpg"; zTextureURL = "qrc:///serverless/Textures/concrete_12-2K/2K-concrete_12-diffuse.jpg"; break; // Rock case 5: xTextureURL = "qrc:///serverless/Textures/Rock026_2K-JPG/Rock026_2K_Color.jpg"; yTextureURL = "qrc:///serverless/Textures/Rock026_2K-JPG/Rock026_2K_Color.jpg"; zTextureURL = "qrc:///serverless/Textures/Rock026_2K-JPG/Rock026_2K_Color.jpg"; break; } updateProperty("xTextureURL", xTextureURL, false); updateProperty("yTextureURL", yTextureURL, false); updateProperty("zTextureURL", zTextureURL, false); }; } /** * 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); } function copySkyboxColorToAmbientColor() { updateProperty("ambientLight.ambientColor", skyboxColorForCopy, false); } function copyPositionProperty() { EventBridge.emitWebEvent(JSON.stringify({ type: "action", action: "copyPosition" })); } function pastePositionProperty() { EventBridge.emitWebEvent(JSON.stringify({ type: "action", action: "pastePosition" })); } function copyRotationProperty() { EventBridge.emitWebEvent(JSON.stringify({ type: "action", action: "copyRotation" })); } function pasteRotationProperty() { EventBridge.emitWebEvent(JSON.stringify({ type: "action", action: "pasteRotation" })); } function setRotationToZeroProperty() { EventBridge.emitWebEvent(JSON.stringify({ type: "action", action: "setRotationToZero" })); } function copyDimensionsProperty() { EventBridge.emitWebEvent(JSON.stringify({ type: "action", action: "copyDimensions" })); } function pasteDimensionsProperty() { EventBridge.emitWebEvent(JSON.stringify({ type: "action", action: "pasteDimensions" })); } /** * 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.} [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.} [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 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.} [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); } function openMaterialAssistant() { if (materialEditor === null) { newJSONMaterialEditor(); } loadDataInMaUi(materialEditor.getText()); $('#uiMaterialAssistant').show(); $('#properties-list').hide(); } function closeMaterialAssistant() { $('#uiMaterialAssistant').hide(); $('#properties-list').show(); } /** * @param {boolean} noUpdate - don't update the UI, but do send a property update. * @param {Set.} [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); } maGetMaterialDataAssistantAvailability(text); } 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 showMaterialAssistantButton() { $('#property-materialData-button-materialAssistant').show(); } function hideMaterialAssistantButton() { $('#property-materialData-button-materialAssistant').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.} [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); } /** * PROCEDURAL PARTICLE DATA FUNCTIONS */ function clearParticleUpdateData() { let elParticleUpdateData = getPropertyInputElement("particleUpdateData"); deleteJSONParticleUpdateEditor(); elParticleUpdateData.value = ""; showParticleUpdateDataTextArea(); showNewJSONParticleUpdateEditorButton(); hideSaveParticleUpdateDataButton(); updateProperty('particleUpdateData', elParticleUpdateData.value, false); } function newJSONParticleUpdateEditor() { getPropertyInputElement("particleUpdateData").classList.remove('multi-diff'); deleteJSONParticleUpdateEditor(); createJSONParticleUpdateEditor(); let data = {}; setParticleUpdateEditorJSON(data); hideParticleUpdateDataTextArea(); hideNewJSONParticleUpdateEditorButton(); showSaveParticleUpdateDataButton(); } /** * @param {Set.} [entityIDsToUpdate] Entity IDs to update particleUpdateData for. */ function saveParticleUpdateData(entityIDsToUpdate) { saveJSONParticleUpdateData(true, entityIDsToUpdate); } function setJSONError(property, isError) { $("#property-"+ property + "-editor").toggleClass('error', isError); let $propertyParticleUpdateDataEditorStatus = $("#property-"+ property + "-editorStatus"); $propertyParticleUpdateDataEditorStatus.css('display', isError ? 'block' : 'none'); $propertyParticleUpdateDataEditorStatus.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.} [entityIDsToUpdate] - Entity IDs to update particleUpdateData for. */ function setParticleUpdateDataFromEditor(noUpdate, entityIDsToUpdate) { let errorFound = false; try { particleUpdateEditor.get(); } catch (e) { errorFound = true; } setJSONError('particleUpdateData', errorFound); if (errorFound) { return; } let text = particleUpdateEditor.getText(); if (noUpdate) { EventBridge.emitWebEvent( JSON.stringify({ ids: [...entityIDsToUpdate], type: "saveParticleUpdateData", properties: { particleUpdateData: text } }) ); } else { updateProperty('particleUpdateData', text, false); } } let particleUpdateEditor = null; function createJSONParticleUpdateEditor() { let container = document.getElementById("property-particleUpdateData-editor"); let options = { search: false, mode: 'tree', modes: ['code', 'tree'], name: 'particleUpdateData', onError: function(e) { alert('JSON editor:' + e); }, onChange: function() { let currentJSONString = particleUpdateEditor.getText(); if (currentJSONString === '{"":""}') { return; } $('#property-particleUpdateData-button-save').attr('disabled', false); } }; particleUpdateEditor = new JSONEditor(container, options); } function showSaveParticleUpdateDataButton() { $('#property-particleUpdateData-button-save').show(); } function hideSaveParticleUpdateDataButton() { $('#property-particleUpdateData-button-save').hide(); } function disableSaveParticleUpdateDataButton() { $('#property-particleUpdateData-button-save').attr('disabled', true); } function showNewJSONParticleUpdateEditorButton() { $('#property-particleUpdateData-button-edit').show(); } function hideNewJSONParticleUpdateEditorButton() { $('#property-particleUpdateData-button-edit').hide(); } function showParticleUpdateDataTextArea() { $('#property-particleUpdateData').show(); } function hideParticleUpdateDataTextArea() { $('#property-particleUpdateData').hide(); } function hideParticleUpdateDataSaved() { $('#property-particleUpdateData-saved').hide(); } function setParticleUpdateEditorJSON(json) { particleUpdateEditor.set(json); if (particleUpdateEditor.hasOwnProperty('expandAll')) { particleUpdateEditor.expandAll(); } } function deleteJSONParticleUpdateEditor() { if (particleUpdateEditor !== null) { setJSONError('particleUpdateData', false); particleUpdateEditor.destroy(); particleUpdateEditor = null; } } let savedParticleUpdateJSONTimer = null; /** * @param {boolean} noUpdate - don't update the UI, but do send a property update. * @param {Set.} [entityIDsToUpdate] Entity IDs to update particleUpdateData for */ function saveJSONParticleUpdateData(noUpdate, entityIDsToUpdate) { setParticleUpdateDataFromEditor(noUpdate, entityIDsToUpdate ? entityIDsToUpdate : selectedEntityIDs); $('#property-particleUpdateData-saved').show(); $('#property-particleUpdateData-button-save').attr('disabled', true); if (savedJSONTimer !== null) { clearTimeout(savedJSONTimer); } savedJSONTimer = setTimeout(function() { hideParticleUpdateDataSaved(); }, EDITOR_TIMEOUT_DURATION); } function clearParticleRenderData() { let elParticleRenderData = getPropertyInputElement("particleRenderData"); deleteJSONParticleRenderEditor(); elParticleRenderData.value = ""; showParticleRenderDataTextArea(); showNewJSONParticleRenderEditorButton(); hideSaveParticleRenderDataButton(); updateProperty('particleRenderData', elParticleRenderData.value, false); } function newJSONParticleRenderEditor() { getPropertyInputElement("particleRenderData").classList.remove('multi-diff'); deleteJSONParticleRenderEditor(); createJSONParticleRenderEditor(); let data = {}; setParticleRenderEditorJSON(data); hideParticleRenderDataTextArea(); hideNewJSONParticleRenderEditorButton(); showSaveParticleRenderDataButton(); } /** * @param {Set.} [entityIDsToUpdate] Entity IDs to update particleRenderData for. */ function saveParticleRenderData(entityIDsToUpdate) { saveJSONParticleRenderData(true, entityIDsToUpdate); } function setJSONError(property, isError) { $("#property-"+ property + "-editor").toggleClass('error', isError); let $propertyParticleRenderDataEditorStatus = $("#property-"+ property + "-editorStatus"); $propertyParticleRenderDataEditorStatus.css('display', isError ? 'block' : 'none'); $propertyParticleRenderDataEditorStatus.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.} [entityIDsToUpdate] - Entity IDs to update particleRenderData for. */ function setParticleRenderDataFromEditor(noUpdate, entityIDsToUpdate) { let errorFound = false; try { particleRenderEditor.get(); } catch (e) { errorFound = true; } setJSONError('particleRenderData', errorFound); if (errorFound) { return; } let text = particleRenderEditor.getText(); if (noUpdate) { EventBridge.emitWebEvent( JSON.stringify({ ids: [...entityIDsToUpdate], type: "saveParticleRenderData", properties: { particleRenderData: text } }) ); } else { updateProperty('particleRenderData', text, false); } } let particleRenderEditor = null; function createJSONParticleRenderEditor() { let container = document.getElementById("property-particleRenderData-editor"); let options = { search: false, mode: 'tree', modes: ['code', 'tree'], name: 'particleRenderData', onError: function(e) { alert('JSON editor:' + e); }, onChange: function() { let currentJSONString = particleRenderEditor.getText(); if (currentJSONString === '{"":""}') { return; } $('#property-particleRenderData-button-save').attr('disabled', false); } }; particleRenderEditor = new JSONEditor(container, options); } function showSaveParticleRenderDataButton() { $('#property-particleRenderData-button-save').show(); } function hideSaveParticleRenderDataButton() { $('#property-particleRenderData-button-save').hide(); } function disableSaveParticleRenderDataButton() { $('#property-particleRenderData-button-save').attr('disabled', true); } function showNewJSONParticleRenderEditorButton() { $('#property-particleRenderData-button-edit').show(); } function hideNewJSONParticleRenderEditorButton() { $('#property-particleRenderData-button-edit').hide(); } function showParticleRenderDataTextArea() { $('#property-particleRenderData').show(); } function hideParticleRenderDataTextArea() { $('#property-particleRenderData').hide(); } function hideParticleRenderDataSaved() { $('#property-particleRenderData-saved').hide(); } function setParticleRenderEditorJSON(json) { particleRenderEditor.set(json); if (particleRenderEditor.hasOwnProperty('expandAll')) { particleRenderEditor.expandAll(); } } function deleteJSONParticleRenderEditor() { if (particleRenderEditor !== null) { setJSONError('particleRenderData', false); particleRenderEditor.destroy(); particleRenderEditor = null; } } let savedParticleRenderJSONTimer = null; /** * @param {boolean} noUpdate - don't update the UI, but do send a property update. * @param {Set.} [entityIDsToUpdate] Entity IDs to update particleRenderData for */ function saveJSONParticleRenderData(noUpdate, entityIDsToUpdate) { setParticleRenderDataFromEditor(noUpdate, entityIDsToUpdate ? entityIDsToUpdate : selectedEntityIDs); $('#property-particleRenderData-saved').show(); $('#property-particleRenderData-button-save').attr('disabled', true); if (savedJSONTimer !== null) { clearTimeout(savedJSONTimer); } savedJSONTimer = setTimeout(function() { hideParticleRenderDataSaved(); }, 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" || e.target.id === "property-particleUpdateData-button-edit" || e.target.id === "property-particleUpdateData-button-clear" || e.target.id === "property-particleRenderData-button-edit" || e.target.id === "property-particleRenderData-button-clear") { return; } if ($('#property-userData-editor').css('height') !== "0px") { saveUserData(); } if ($('#property-materialData-editor').css('height') !== "0px") { saveMaterialData(); } if ($('#property-particleUpdateData-editor').css('height') !== "0px") { saveParticleUpdateData(); } if ($('#property-particleRenderData-editor').css('height') !== "0px") { saveParticleRenderData(); } }); } } /** * 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"); } /** * ZONE SELECTOR FUNCTIONS */ function enableAllMultipleZoneSelector() { let allMultiZoneSelectors = document.querySelectorAll(".hiddenMultiZonesSelection"); let i, propId; for (i = 0; i < allMultiZoneSelectors.length; i++) { propId = allMultiZoneSelectors[i].id; displaySelectedZones(propId, true); } } function disableAllMultipleZoneSelector() { let allMultiZoneSelectors = document.querySelectorAll(".hiddenMultiZonesSelection"); let i, propId; for (i = 0; i < allMultiZoneSelectors.length; i++) { propId = allMultiZoneSelectors[i].id; displaySelectedZones(propId, false); } } function requestZoneList() { EventBridge.emitWebEvent(JSON.stringify({ type: "zoneListRequest" })); } function addZoneToZonesSelection(propertyId, id) { let hiddenField = document.getElementById(propertyId); if (JSON.stringify(hiddenField.value) === '"undefined"') { hiddenField.value = "[]"; } let zonesCoefficientID = document.getElementById(propertyId).getAttribute('zonesCoefficientID'); let selectedZones = JSON.parse(hiddenField.value); if (id === "ALL") { for (let i = 0; i < zonesList.length; i++) { if (!selectedZones.includes(zonesList[i].id)) { selectedZones.push(zonesList[i].id); if (zonesCoefficientID !== "") { addElementToZonesCoefficient("property-" + zonesCoefficientID.replace(".", "-")); } } } } else { if (!selectedZones.includes(id)) { selectedZones.push(id); if (zonesCoefficientID !== "") { addElementToZonesCoefficient("property-" + zonesCoefficientID.replace(".", "-")); } } } hiddenField.value = JSON.stringify(selectedZones); displaySelectedZones(propertyId, true); let propertyName = propertyId.replace("property-", ""); propertyName = propertyName.replace("-", "."); updateProperty(propertyName, selectedZones, false); document.getElementById("zones-select-selector-list-panel-" + propertyId).style.display = "none"; } function removeZoneFromZonesSelection(propertyId, zoneId) { let zonesCoefficientID = document.getElementById(propertyId).getAttribute('zonesCoefficientID'); let hiddenField = document.getElementById(propertyId); if (JSON.stringify(hiddenField.value) === '"undefined"') { hiddenField.value = "[]"; } let selectedZones = JSON.parse(hiddenField.value); let index = selectedZones.indexOf(zoneId); if (index > -1) { selectedZones.splice(index, 1); if (zonesCoefficientID !== "") { removeElementFromZonesCoefficient("property-" + zonesCoefficientID.replace(".", "-"), index); } } hiddenField.value = JSON.stringify(selectedZones); displaySelectedZones(propertyId, true); let propertyName = propertyId.replace("property-", ""); propertyName = propertyName.replace("-", "."); updateProperty(propertyName, selectedZones, false); } function displaySelectedZones(propertyId, isEditable) { let i,j, name, listedZoneInner, hiddenData, isMultiple; hiddenData = document.getElementById(propertyId).value; if (JSON.stringify(hiddenData) === '"undefined"') { isMultiple = true; hiddenData = "[]"; } else { isMultiple = false; } let selectedZones = JSON.parse(hiddenData); listedZoneInner = ""; if (selectedZones.length === 0) { if (!isMultiple) { listedZoneInner += ""; } else { listedZoneInner += ""; } } else { for (i = 0; i < selectedZones.length; i++) { name = getZoneName(selectedZones[i]); if (isEditable) { listedZoneInner += ""; } else { listedZoneInner += ""; } } } listedZoneInner += "
  
[ WARNING: Any changes will apply to all. ] 
" + name + ""; listedZoneInner += "
" + name + " 
"; document.getElementById("selected-zones-" + propertyId).innerHTML = listedZoneInner; if (isEditable) { document.getElementById("multiZoneSelTools-" + propertyId).style.display = "block"; } else { document.getElementById("multiZoneSelTools-" + propertyId).style.display = "none"; } } function createZonesSelection(property, elProperty) { let propertyData = property.data; let elementID = property.elementID; requestZoneList(); elProperty.className = "multipleZonesSelection"; let elInput = document.createElement('input'); elInput.setAttribute("id", elementID); elInput.setAttribute("type", "hidden"); elInput.setAttribute("zonesCoefficientID", propertyData.zonesCoefficientID ?? ""); elInput.className = "hiddenMultiZonesSelection"; let elZonesSelector = document.createElement('div'); elZonesSelector.setAttribute("id", "zones-selector-" + elementID); let elMultiDiff = document.createElement('span'); elMultiDiff.className = "multi-diff"; elProperty.appendChild(elInput); elProperty.appendChild(elZonesSelector); elProperty.appendChild(elMultiDiff); return elInput; } function setZonesSelectionData(element, isEditable) { let zoneSelectorContainer = document.getElementById("zones-selector-" + element.id); let zoneSelector = "
"; zoneSelector += ""; zoneSelector += "
"; zoneSelector += "
Select the Zone to add:"; zoneSelector += ""; zoneSelector += "
"; zoneSelector += "
"; let i, name; for (i = 0; i < zonesList.length; i++) { if (zonesList[i].name === "") { name = zonesList[i].id; } else { name = zonesList[i].name; } zoneSelector += "
"; } zoneSelector += "
"; zoneSelector += "
"; zoneSelector += ""; zoneSelector += "
"; zoneSelector += "
"; zoneSelectorContainer.innerHTML = zoneSelector; displaySelectedZones(element.id, isEditable); } function getZoneName(zoneID) { let name = "{ERROR: NOT FOUND}"; let j; for (j = 0; j < zonesList.length; j++) { if (zoneID === zonesList[j].id) { if (zonesList[j].name !== "") { name = zonesList[j].name; } else { name = zonesList[j].id; } break; } } return name; } /** * ZONES COEFFICIENT */ function createZonesCoefficient(property, elProperty) { let propertyData = property.data; let elementID = property.elementID; elProperty.className = "zonesCoefficient"; let elInput = document.createElement('input'); elInput.setAttribute("id", elementID); elInput.setAttribute("type", "hidden"); elInput.setAttribute("multipleZonesSelectionID", propertyData.multipleZonesSelectionID); elInput.className = "hiddenZonesCoefficient"; let elZonesCoefficientSelector = document.createElement('div'); elZonesCoefficientSelector.setAttribute("id", "zonesCoefficient-selector-" + elementID); let elMultiDiff = document.createElement('span'); elMultiDiff.className = "multi-diff"; elProperty.appendChild(elInput); elProperty.appendChild(elZonesCoefficientSelector); elProperty.appendChild(elMultiDiff); return elInput; } function setZonesCoefficientUi(propertyId, isEditable) { let i, listedCoefficientsInner, hiddenData, isMultiple, multipleZonesSelectionID, zoneListData; hiddenData = document.getElementById(propertyId).value; multipleZonesSelectionID = document.getElementById(propertyId).getAttribute('multipleZonesSelectionID'); zoneListData = JSON.parse(document.getElementById("property-" + multipleZonesSelectionID.replace(".", "-")).value); if (JSON.stringify(hiddenData) === '"undefined"') { isMultiple = true; hiddenData = "[]"; } else { isMultiple = false; } listedCoefficientsInner = "
"; let coefficients = JSON.parse(hiddenData); if (coefficients.length !== zoneListData.length) { listedCoefficientsInner += "
"; listedCoefficientsInner += "WARNING: Zones and coefficients are desynchronized.
"; listedCoefficientsInner += ""; listedCoefficientsInner += "
"; } else { if (coefficients.length === 0) { if (isMultiple) { listedCoefficientsInner += "Not available."; } listedCoefficientsInner += "
"; } else { let zoneName, lineColor; for (i = 0; i < coefficients.length; i++) { zoneName = getZoneName(zoneListData[i]); lineColor = ""; if (i % 2 !== 0) { lineColor = " zonesCoefficientDarkLine"; } if (isEditable) { listedCoefficientsInner += "
"; listedCoefficientsInner += ""; listedCoefficientsInner += ""; listedCoefficientsInner += ""; listedCoefficientsInner += " " + zoneName + ""; listedCoefficientsInner += "
"; } else { listedCoefficientsInner += "
"; listedCoefficientsInner += "
" + +coefficients[i].toFixed(4) + "
"; listedCoefficientsInner += "  " + zoneName + ""; listedCoefficientsInner += "
"; } } } } listedCoefficientsInner += "
"; document.getElementById("zonesCoefficient-selector-" + propertyId).innerHTML = listedCoefficientsInner; } function fixUnsynchZonesCoefficients(propertyId, expectedLength) { let hiddenField = document.getElementById(propertyId); if (JSON.stringify(hiddenField.value) === '"undefined"') { hiddenField.value = "[]"; } let originalCoefficients = JSON.parse(hiddenField.value); let coefficients = []; let i; for (i = 0; i < expectedLength; i++) { if ( i < originalCoefficients.length) { coefficients[i] = originalCoefficients[i]; } else { coefficients[i] = AUDIO_ATTENUATION_COEFFICIENT_DEFAULT_VALUE; } } hiddenField.value = JSON.stringify(coefficients); let propertyName = propertyId.replace("property-", ""); propertyName = propertyName.replace("-", "."); updateProperty(propertyName, coefficients, false); } function setZonesCoefficientValue(propertyId, index, coef) { function cleanFloat(val) { let num = parseFloat(val); if (Number.isNaN(num)) { return 0; } return num; } let newCoefValue = cleanFloat(coef); let hiddenField = document.getElementById(propertyId); if (JSON.stringify(hiddenField.value) === '"undefined"') { hiddenField.value = "[]"; } let coefficients = JSON.parse(hiddenField.value); coefficients[index] = newCoefValue; hiddenField.value = JSON.stringify(coefficients); let propertyName = propertyId.replace("property-", ""); propertyName = propertyName.replace("-", "."); updateProperty(propertyName, coefficients, false); } function removeElementFromZonesCoefficient(propertyId, index) { let hiddenField = document.getElementById(propertyId); if (JSON.stringify(hiddenField.value) === '"undefined"') { hiddenField.value = "[]"; } let coefficients = JSON.parse(hiddenField.value); coefficients.splice(index, 1); hiddenField.value = JSON.stringify(coefficients); let propertyName = propertyId.replace("property-", ""); propertyName = propertyName.replace("-", "."); updateProperty(propertyName, coefficients, false); } function addElementToZonesCoefficient(propertyId) { let hiddenField = document.getElementById(propertyId); if (JSON.stringify(hiddenField.value) === '"undefined"') { hiddenField.value = "[]"; } let coefficients = JSON.parse(hiddenField.value); coefficients.push(AUDIO_ATTENUATION_COEFFICIENT_DEFAULT_VALUE); hiddenField.value = JSON.stringify(coefficients); let propertyName = propertyId.replace("property-", ""); propertyName = propertyName.replace("-", "."); updateProperty(propertyName, coefficients, false); } /** * ARRAY-OF-STRINGS FUNCTIONS */ function createArrayOfStrings(property, elProperty) { let propertyData = property.data; let elementID = property.elementID; elProperty.className = "arrayOfStrings"; let elInput = document.createElement('input'); elInput.setAttribute("id", elementID); elInput.setAttribute("type", "hidden"); elInput.setAttribute("useStringColor", propertyData.useStringColor); elInput.className = "hiddenArrayOfStrings"; let elArrayOfStringsSelector = document.createElement('div'); elArrayOfStringsSelector.setAttribute("id", "arrayOfStrings-selector-" + elementID); let elMultiDiff = document.createElement('span'); elMultiDiff.className = "multi-diff"; elProperty.appendChild(elInput); elProperty.appendChild(elArrayOfStringsSelector); elProperty.appendChild(elMultiDiff); return elInput; } function setArrayOfStringsUi(propertyId, isEditable) { let i, listedStringsInner, hiddenData, isMultiple, useStringColor, tagStyle; hiddenData = document.getElementById(propertyId).value; useStringColor = document.getElementById(propertyId).getAttribute('useStringColor').toLowerCase() === "true"; if (JSON.stringify(hiddenData) === '"undefined"') { isMultiple = true; hiddenData = "[]"; } else { isMultiple = false; } listedStringsInner = "
"; let selectedStrings = JSON.parse(hiddenData); if (selectedStrings.length === 0) { if (isMultiple) { listedStringsInner += "
"; //or anything saying we dont suport multiple selection, but we might by the list with bulk actions. } } else { selectedStrings.sort(); let counter = 0; for (i = 0; i < selectedStrings.length; i++) { tagStyle = ""; if (useStringColor) { tagStyle = " style='color:#bbbbbb; background-color:" + getColorOfString(selectedStrings[i]) + ";'"; } if (isEditable) { listedStringsInner += "
" + selectedStrings[i] + "  "; listedStringsInner += "✖" listedStringsInner += "
"; } else { listedStringsInner += "
" + selectedStrings[i] + "
"; } counter++; if (counter === MAX_TAGS_PER_ROWS) { listedStringsInner += "
"; counter = 0; } } } if (isEditable && !isMultiple) { let addComponent = "
"; addComponent += " "; addComponent += "
"; listedStringsInner += addComponent; } listedStringsInner += "
"; document.getElementById("arrayOfStrings-selector-" + propertyId).innerHTML = listedStringsInner; } function removeElementFromArrayOfStrings(propertyId, stringText) { let hiddenField = document.getElementById(propertyId); if (JSON.stringify(hiddenField.value) === '"undefined"') { hiddenField.value = "[]"; } let selectedStrings = JSON.parse(hiddenField.value); let index = selectedStrings.indexOf(stringText); if (index > -1) { selectedStrings.splice(index, 1); } hiddenField.value = JSON.stringify(selectedStrings); setArrayOfStringsUi(propertyId, true); let propertyName = propertyId.replace("property-", ""); propertyName = propertyName.replace("-", "."); updateProperty(propertyName, selectedStrings, false); } function addElementToArrayOfStrings(propertyId, elementToGetText) { let stringText = document.getElementById(elementToGetText).value; if (stringText === "") { return; } else { let hiddenField = document.getElementById(propertyId); if (JSON.stringify(hiddenField.value) === '"undefined"') { hiddenField.value = "[]"; } let selectedStrings = JSON.parse(hiddenField.value); if (!selectedStrings.includes(stringText)) { selectedStrings.push(stringText); } hiddenField.value = JSON.stringify(selectedStrings); setArrayOfStringsUi(propertyId, true); let propertyName = propertyId.replace("property-", ""); propertyName = propertyName.replace("-", "."); updateProperty(propertyName, selectedStrings, false); } } function getColorOfString(input) { let sum = 0; for (let i = 0; i < input.length; i++) { sum += input.charCodeAt(i); } let hue = sum % 360; return hslToHex(hue, 100, 20); } function hslToHex(h, s, l) { s /= 100; l /= 100; const c = (1 - Math.abs(2 * l - 1)) * s; const x = c * (1 - Math.abs((h / 60) % 2 - 1)); const m = l - c / 2; let r = 0, g = 0, b = 0; if (0 <= h && h < 60) { [r, g, b] = [c, x, 0]; } else if (60 <= h && h < 120) { [r, g, b] = [x, c, 0]; } else if (120 <= h && h < 180) { [r, g, b] = [0, c, x]; } else if (180 <= h && h < 240) { [r, g, b] = [0, x, c]; } else if (240 <= h && h < 300) { [r, g, b] = [x, 0, c]; } else if (300 <= h && h < 360) { [r, g, b] = [c, 0, x]; } const toHex = (n) => { const hex = Math.round((n + m) * 255).toString(16); return hex.padStart(2, '0'); }; return `#${toHex(r)}${toHex(g)}${toHex(b)}`; } /** * CHILD ENTITIES FUNCTIONS */ function createChildList(property, elProperty) { let elementID = property.elementID; elProperty.className = "childEntityList"; let elInput = document.createElement('div'); elInput.setAttribute("id", "childList-" + elementID); elProperty.appendChild(elInput); return elInput; } function setChildListData(element, children, parentID, entityHostType = "") { let childListContainer = document.getElementById(element.id); let renderer = ""; let i; renderer += "
"; if (parentID !== "") { renderer += "
1View Parent
"; } else { renderer += "
"; } if ((entityHostType === "domain" || entityHostType === "avatar") && children !== undefined) { renderer += "
+ Add Child Entity  
"; } else { renderer += "
"; } renderer += "
"; renderer += ""; renderer += ""; if (children === undefined) { renderer += ""; } else { if (children.length > 0) { for (i = 0; i < children.length; i++ ) { let entityHostTypeClass = ""; if (children[i].entityHostType !== "domain") { entityHostTypeClass = " class='" + children[i].entityHostType + "Entity'"; } let navigatorBtn = "🡆"; renderer += ""; } } else { renderer += ""; } } renderer += "
TYPENAMEVIEW
Not applicable
" + children[i].type + "" + children[i].name + "" + navigatorBtn + "
No children
"; childListContainer.innerHTML = renderer; } function navigateToSpecificEntityFromParentID() { let parentID = getPropertyInputElement("parentID").value; if (parentID !== "" && parentID !== UUID_NONE) { navigateToSpecificEntity(parentID); } } function setParentIdNavigationAvailable(selectionLength) { $('#property-parentID-button-navigateToParentEntity').attr('disabled', selectionLength !== 1); } function navigateToSpecificEntity(id) { EventBridge.emitWebEvent(JSON.stringify({ type: "specificEntityNavigation", id: id })); } //CREATE CHILD ENTITY FUNCTIONS: function openCreateChildEntityAssistant(entityHostType) { $('#uiCreateChildEntityAssistant').show(); $('#properties-list').hide(); generateCreateChildEntityAssistant(entityHostType); } function generateCreateChildEntityAssistant(entityHostType) { let entityType = [ {"type": "Model", "name": "Model"}, {"type": "Shape", "name": "Shape"}, {"type": "Text", "name": "Text"}, {"type": "Image", "name": "Image"}, {"type": "Web", "name": "Web"}, {"type": "ParticleEffect", "name": "Particle"}, {"type": "ProceduralParticleEffect", "name": "Proc. Part."}, {"type": "Light", "name": "Light"}, {"type": "Zone", "name": "Zone"}, {"type": "Material", "name": "Material"}, {"type": "Sound", "name": "Sound"}, {"type": "Script", "name": "Script"}, {"type": "PolyVox", "name": "Voxel"}, {"type": "Empty", "name": "Empty"}, ]; const TILES_PER_ROW = 4; let renderer = "
"; let i; let rowCount = 0; for (i = 0; i < entityType.length; i++) { if (rowCount === 0) { renderer += "
"; } rowCount++; renderer += ""; renderer += "" + ENTITY_TYPE_ICON[entityType[i].type] + "
" + entityType[i].name.toUpperCase() + "
"; if (rowCount === TILES_PER_ROW) { renderer += "
"; rowCount = 0; } } if (rowCount !== 0) { renderer += "
"; } renderer += ""; renderer += "