} [entityIDsToUpdate] - Entity IDs to update materialData for.
*/
function saveJSONMaterialData(noUpdate, entityIDsToUpdate) {
setMaterialDataFromEditor(noUpdate, entityIDsToUpdate ? entityIDsToUpdate : selectedEntityIDs);
$('#property-materialData-saved').show();
$('#property-materialData-button-save').attr('disabled', true);
if (savedMaterialJSONTimer !== null) {
clearTimeout(savedMaterialJSONTimer);
}
savedMaterialJSONTimer = setTimeout(function() {
hideMaterialDataSaved();
}, EDITOR_TIMEOUT_DURATION);
}
function bindAllNonJSONEditorElements() {
let inputs = $('input');
let i;
for (i = 0; i < inputs.length; ++i) {
let input = inputs[i];
let field = $(input);
// TODO FIXME: (JSHint) Functions declared within loops referencing
// an outer scoped variable may lead to confusing semantics.
field.on('focus', function(e) {
if (e.target.id === "property-userData-button-edit" || e.target.id === "property-userData-button-clear" ||
e.target.id === "property-materialData-button-edit" || e.target.id === "property-materialData-button-clear") {
return;
}
if ($('#property-userData-editor').css('height') !== "0px") {
saveUserData();
}
if ($('#property-materialData-editor').css('height') !== "0px") {
saveMaterialData();
}
});
}
}
/**
* DROPDOWN FUNCTIONS
*/
function setDropdownText(dropdown) {
let lis = dropdown.parentNode.getElementsByTagName("li");
let text = "";
for (let i = 0; i < lis.length; ++i) {
if (String(lis[i].getAttribute("value")) === String(dropdown.value)) {
text = lis[i].textContent;
}
}
dropdown.firstChild.textContent = text;
}
function toggleDropdown(event) {
let element = event.target;
if (element.nodeName !== "DT") {
element = element.parentNode;
}
element = element.parentNode;
let isDropped = element.getAttribute("dropped");
element.setAttribute("dropped", isDropped !== "true" ? "true" : "false");
}
function closeAllDropdowns() {
let elDropdowns = document.querySelectorAll("div.dropdown > dl");
for (let i = 0; i < elDropdowns.length; ++i) {
elDropdowns[i].setAttribute('dropped', 'false');
}
}
function setDropdownValue(event) {
let dt = event.target.parentNode.parentNode.previousSibling.previousSibling;
dt.value = event.target.getAttribute("value");
dt.firstChild.textContent = event.target.textContent;
dt.parentNode.setAttribute("dropped", "false");
let evt = document.createEvent("HTMLEvents");
evt.initEvent("change", true, true);
dt.dispatchEvent(evt);
}
/**
* TEXTAREA FUNCTIONS
*/
function setTextareaScrolling(element) {
let isScrolling = element.scrollHeight > element.offsetHeight;
element.setAttribute("scrolling", isScrolling ? "true" : "false");
}
/**
* MATERIAL TARGET FUNCTIONS
*/
function requestMaterialTarget() {
EventBridge.emitWebEvent(JSON.stringify({
type: 'materialTargetRequest',
entityID: getFirstSelectedID(),
}));
}
function setMaterialTargetData(materialTargetData) {
let elDivOptions = getPropertyInputElement("parentMaterialName");
resetDynamicMultiselectProperty(elDivOptions);
if (materialTargetData === undefined) {
return;
}
elDivOptions.firstChild.style.display = "none"; // hide "No Options" text
elDivOptions.parentNode.lastChild.style.display = null; // show Select/Clear all buttons
let numMeshes = materialTargetData.numMeshes;
for (let i = 0; i < numMeshes; ++i) {
addMaterialTarget(elDivOptions, i, false);
}
let materialNames = materialTargetData.materialNames;
let materialNamesAdded = [];
for (let i = 0; i < materialNames.length; ++i) {
let materialName = materialNames[i];
if (materialNamesAdded.indexOf(materialName) === -1) {
addMaterialTarget(elDivOptions, materialName, true);
materialNamesAdded.push(materialName);
}
}
materialTargetPropertyUpdate(elDivOptions.propertyValue);
}
function addMaterialTarget(elDivOptions, targetID, isMaterialName) {
let elementID = elDivOptions.getAttribute("id");
elementID += isMaterialName ? "-material-" : "-mesh-";
elementID += targetID;
let elDiv = document.createElement('div');
elDiv.className = "materialTargetDiv";
elDiv.onclick = onToggleMaterialTarget;
elDivOptions.appendChild(elDiv);
let elInput = document.createElement('input');
elInput.className = "materialTargetInput";
elInput.setAttribute("type", "checkbox");
elInput.setAttribute("id", elementID);
elInput.setAttribute("targetID", targetID);
elInput.setAttribute("isMaterialName", isMaterialName);
elDiv.appendChild(elInput);
let elLabel = document.createElement('label');
elLabel.setAttribute("for", elementID);
elLabel.innerText = isMaterialName ? "Material " + targetID : "Mesh Index " + targetID;
elDiv.appendChild(elLabel);
return elDiv;
}
function onToggleMaterialTarget(event) {
let elTarget = event.target;
if (elTarget instanceof HTMLInputElement) {
sendMaterialTargetProperty();
}
event.stopPropagation();
}
function setAllMaterialTargetInputs(checked) {
let elDivOptions = getPropertyInputElement("parentMaterialName");
let elInputs = elDivOptions.getElementsByClassName("materialTargetInput");
for (let i = 0; i < elInputs.length; ++i) {
elInputs[i].checked = checked;
}
}
function selectAllMaterialTarget() {
setAllMaterialTargetInputs(true);
sendMaterialTargetProperty();
}
function clearAllMaterialTarget() {
setAllMaterialTargetInputs(false);
sendMaterialTargetProperty();
}
function sendMaterialTargetProperty() {
let elDivOptions = getPropertyInputElement("parentMaterialName");
let elInputs = elDivOptions.getElementsByClassName("materialTargetInput");
let materialTargetList = [];
for (let i = 0; i < elInputs.length; ++i) {
let elInput = elInputs[i];
if (elInput.checked) {
let targetID = elInput.getAttribute("targetID");
if (elInput.getAttribute("isMaterialName") === "true") {
materialTargetList.push(MATERIAL_PREFIX_STRING + targetID);
} else {
materialTargetList.push(targetID);
}
}
}
let propertyValue = materialTargetList.join(",");
if (propertyValue.length > 1) {
propertyValue = "[" + propertyValue + "]";
}
updateProperty("parentMaterialName", propertyValue, false);
}
function materialTargetPropertyUpdate(propertyValue) {
let elDivOptions = getPropertyInputElement("parentMaterialName");
let elInputs = elDivOptions.getElementsByClassName("materialTargetInput");
if (propertyValue.startsWith('[')) {
propertyValue = propertyValue.substring(1, propertyValue.length);
}
if (propertyValue.endsWith(']')) {
propertyValue = propertyValue.substring(0, propertyValue.length - 1);
}
let materialTargets = propertyValue.split(",");
for (let i = 0; i < elInputs.length; ++i) {
let elInput = elInputs[i];
let targetID = elInput.getAttribute("targetID");
let materialTargetName = targetID;
if (elInput.getAttribute("isMaterialName") === "true") {
materialTargetName = MATERIAL_PREFIX_STRING + targetID;
}
elInput.checked = materialTargets.indexOf(materialTargetName) >= 0;
}
elDivOptions.propertyValue = propertyValue;
}
function roundAndFixNumber(number, propertyData) {
let result = number;
if (propertyData.round !== undefined) {
result = Math.round(result * propertyData.round) / propertyData.round;
}
if (propertyData.decimals !== undefined) {
return result.toFixed(propertyData.decimals)
}
return result;
}
function applyInputNumberPropertyModifiers(number, propertyData) {
const multiplier = propertyData.multiplier !== undefined ? propertyData.multiplier : 1;
return roundAndFixNumber(number / multiplier, propertyData);
}
function applyOutputNumberPropertyModifiers(number, propertyData) {
const multiplier = propertyData.multiplier !== undefined ? propertyData.multiplier : 1;
return roundAndFixNumber(number * multiplier, propertyData);
}
const areSetsEqual = (a, b) => a.size === b.size && [...a].every(value => b.has(value));
function handleEntitySelectionUpdate(selections, isPropertiesToolUpdate) {
const previouslySelectedEntityIDs = selectedEntityIDs;
currentSelections = selections;
selectedEntityIDs = new Set(selections.map(selection => selection.id));
const multipleSelections = currentSelections.length > 1;
const hasSelectedEntityChanged = !areSetsEqual(selectedEntityIDs, previouslySelectedEntityIDs);
if (selections.length === 0) {
deleteJSONEditor();
deleteJSONMaterialEditor();
resetProperties();
showGroupsForType("None");
let elIcon = properties.type.elSpan;
elIcon.innerText = NO_SELECTION;
elIcon.style.display = 'inline-block';
getPropertyInputElement("userData").value = "";
showUserDataTextArea();
showSaveUserDataButton();
showNewJSONEditorButton();
getPropertyInputElement("materialData").value = "";
showMaterialDataTextArea();
showSaveMaterialDataButton();
showNewJSONMaterialEditorButton();
disableProperties();
} else {
if (!isPropertiesToolUpdate && !hasSelectedEntityChanged && document.hasFocus()) {
// in case the selection has not changed and we still have focus on the properties page,
// we will ignore the event.
return;
}
if (hasSelectedEntityChanged) {
if (!multipleSelections) {
resetServerScriptStatus();
}
}
const doSelectElement = !hasSelectedEntityChanged;
// Get unique entity types, and convert the types Sphere and Box to Shape
const shapeTypes = ["Sphere", "Box"];
const entityTypes = [...new Set(currentSelections.map(a =>
shapeTypes.includes(a.properties.type) ? "Shape" : a.properties.type))];
const shownGroups = getGroupsForTypes(entityTypes);
showGroupsForTypes(entityTypes);
const lockedMultiValue = getMultiplePropertyValue('locked');
if (lockedMultiValue.isMultiDiffValue || lockedMultiValue.value) {
disableProperties();
getPropertyInputElement('locked').removeAttribute('disabled');
} else {
enableProperties();
disableSaveUserDataButton();
disableSaveMaterialDataButton()
}
const certificateIDMultiValue = getMultiplePropertyValue('certificateID');
const hasCertifiedInSelection = certificateIDMultiValue.isMultiDiffValue || certificateIDMultiValue.value !== "";
Object.entries(properties).forEach(function([propertyID, property]) {
const propertyData = property.data;
const propertyName = property.name;
let propertyMultiValue = getMultiplePropertyValue(propertyName);
let isMultiDiffValue = propertyMultiValue.isMultiDiffValue;
let propertyValue = propertyMultiValue.value;
if (propertyData.selectionVisibility !== undefined) {
let visibility = propertyData.selectionVisibility;
let propertyVisible = true;
if (!multipleSelections) {
propertyVisible = isFlagSet(visibility, PROPERTY_SELECTION_VISIBILITY.SINGLE_SELECTION);
} else if (isMultiDiffValue) {
propertyVisible = isFlagSet(visibility, PROPERTY_SELECTION_VISIBILITY.MULTI_DIFF_SELECTIONS);
} else {
propertyVisible = isFlagSet(visibility, PROPERTY_SELECTION_VISIBILITY.MULTIPLE_SELECTIONS);
}
setPropertyVisibility(property, propertyVisible);
}
const isSubProperty = propertyData.subPropertyOf !== undefined;
if (propertyValue === undefined && !isMultiDiffValue && !isSubProperty) {
return;
}
if (!shownGroups.includes(property.group_id)) {
const WANT_DEBUG_SHOW_HIDDEN_FROM_GROUPS = false;
if (WANT_DEBUG_SHOW_HIDDEN_FROM_GROUPS) {
console.log("Skipping property " + property.data.label + " [" + property.name +
"] from hidden group " + property.group_id);
}
return;
}
if (propertyData.hideIfCertified && hasCertifiedInSelection) {
propertyValue = "** Certified **";
property.elInput.disabled = true;
}
if (propertyName === "type") {
propertyValue = entityTypes.length > 1 ? "Multiple" : propertyMultiValue.values[0];
}
switch (propertyData.type) {
case 'string': {
if (isMultiDiffValue) {
if (propertyData.readOnly && propertyData.multiDisplayMode
&& propertyData.multiDisplayMode === PROPERTY_MULTI_DISPLAY_MODE.COMMA_SEPARATED_VALUES) {
property.elInput.value = propertyMultiValue.values.join(", ");
} else {
property.elInput.classList.add('multi-diff');
property.elInput.value = "";
}
} else {
property.elInput.classList.remove('multi-diff');
property.elInput.value = propertyValue;
}
break;
}
case 'bool': {
const inverse = propertyData.inverse !== undefined ? propertyData.inverse : false;
if (isSubProperty) {
let subPropertyMultiValue = getMultiplePropertyValue(propertyData.subPropertyOf);
let propertyValue = subPropertyMultiValue.value;
isMultiDiffValue = subPropertyMultiValue.isMultiDiffValue;
if (isMultiDiffValue) {
let detailedSubProperty = getDetailedSubPropertyMPVDiff(subPropertyMultiValue, propertyName);
property.elInput.checked = detailedSubProperty.isChecked;
property.elInput.classList.toggle('multi-diff', detailedSubProperty.isMultiDiff);
} else {
let subProperties = propertyValue.split(",");
let subPropertyValue = subProperties.indexOf(propertyName) > -1;
property.elInput.checked = inverse ? !subPropertyValue : subPropertyValue;
property.elInput.classList.remove('multi-diff');
}
} else {
if (isMultiDiffValue) {
property.elInput.checked = false;
} else {
property.elInput.checked = inverse ? !propertyValue : propertyValue;
}
property.elInput.classList.toggle('multi-diff', isMultiDiffValue);
}
break;
}
case 'number': {
property.elInput.value = isMultiDiffValue ? "" : propertyValue;
property.elInput.classList.toggle('multi-diff', isMultiDiffValue);
break;
}
case 'number-draggable': {
let detailedNumberDiff = getDetailedNumberMPVDiff(propertyMultiValue, propertyData);
property.elNumber.setValue(detailedNumberDiff.averagePerPropertyComponent[0], detailedNumberDiff.propertyComponentDiff[0]);
break;
}
case 'rect': {
let detailedNumberDiff = getDetailedNumberMPVDiff(propertyMultiValue, propertyData);
property.elNumberX.setValue(detailedNumberDiff.averagePerPropertyComponent.x, detailedNumberDiff.propertyComponentDiff.x);
property.elNumberY.setValue(detailedNumberDiff.averagePerPropertyComponent.y, detailedNumberDiff.propertyComponentDiff.y);
property.elNumberWidth.setValue(detailedNumberDiff.averagePerPropertyComponent.width, detailedNumberDiff.propertyComponentDiff.width);
property.elNumberHeight.setValue(detailedNumberDiff.averagePerPropertyComponent.height, detailedNumberDiff.propertyComponentDiff.height);
break;
}
case 'vec3':
case 'vec2': {
let detailedNumberDiff = getDetailedNumberMPVDiff(propertyMultiValue, propertyData);
property.elNumberX.setValue(detailedNumberDiff.averagePerPropertyComponent.x, detailedNumberDiff.propertyComponentDiff.x);
property.elNumberY.setValue(detailedNumberDiff.averagePerPropertyComponent.y, detailedNumberDiff.propertyComponentDiff.y);
if (property.elNumberZ !== undefined) {
property.elNumberZ.setValue(detailedNumberDiff.averagePerPropertyComponent.z, detailedNumberDiff.propertyComponentDiff.z);
}
break;
}
case 'color': {
let displayColor = propertyMultiValue.isMultiDiffValue ? propertyMultiValue.values[0] : propertyValue;
property.elColorPicker.style.backgroundColor = "rgb(" + displayColor.red + "," +
displayColor.green + "," +
displayColor.blue + ")";
property.elColorPicker.classList.toggle('multi-diff', propertyMultiValue.isMultiDiffValue);
if (hasSelectedEntityChanged && $(property.elColorPicker).attr('active') === 'true') {
// Set the color picker inactive before setting the color,
// otherwise an update will be sent directly after setting it here.
$(property.elColorPicker).attr('active', 'false');
colorPickers['#' + property.elementID].colpickSetColor({
"r": displayColor.red,
"g": displayColor.green,
"b": displayColor.blue
});
$(property.elColorPicker).attr('active', 'true');
}
property.elNumberR.setValue(displayColor.red);
property.elNumberG.setValue(displayColor.green);
property.elNumberB.setValue(displayColor.blue);
break;
}
case 'dropdown': {
property.elInput.classList.toggle('multi-diff', isMultiDiffValue);
property.elInput.value = isMultiDiffValue ? "" : propertyValue;
setDropdownText(property.elInput);
break;
}
case 'textarea': {
property.elInput.value = propertyValue;
setTextareaScrolling(property.elInput);
break;
}
case 'icon': {
property.elSpan.innerHTML = propertyData.icons[propertyValue];
property.elSpan.style.display = "inline-block";
break;
}
case 'texture': {
property.elInput.value = isMultiDiffValue ? "" : propertyValue;
property.elInput.classList.toggle('multi-diff', isMultiDiffValue);
if (isMultiDiffValue) {
property.elInput.setMultipleValues();
} else {
property.elInput.imageLoad(property.elInput.value);
}
break;
}
case 'dynamic-multiselect': {
if (!isMultiDiffValue && property.data.propertyUpdate) {
property.data.propertyUpdate(propertyValue);
}
break;
}
}
let showPropertyRules = property.showPropertyRules;
if (showPropertyRules !== undefined) {
for (let propertyToShow in showPropertyRules) {
let showIfThisPropertyValue = showPropertyRules[propertyToShow];
let show = String(propertyValue) === String(showIfThisPropertyValue);
showPropertyElement(propertyToShow, show);
}
}
});
updateVisibleSpaceModeProperties();
let userDataMultiValue = getMultiplePropertyValue("userData");
let userDataTextArea = getPropertyInputElement("userData");
let json = null;
if (!userDataMultiValue.isMultiDiffValue) {
try {
json = JSON.parse(userDataMultiValue.value);
} catch (e) {
}
}
if (json !== null) {
if (editor === null) {
createJSONEditor();
}
userDataTextArea.classList.remove('multi-diff');
setEditorJSON(json);
showSaveUserDataButton();
hideUserDataTextArea();
hideNewJSONEditorButton();
hideUserDataSaved();
} else {
// normal text
deleteJSONEditor();
userDataTextArea.classList.toggle('multi-diff', userDataMultiValue.isMultiDiffValue);
userDataTextArea.value = userDataMultiValue.isMultiDiffValue ? "" : userDataMultiValue.value;
showUserDataTextArea();
showNewJSONEditorButton();
hideSaveUserDataButton();
hideUserDataSaved();
}
let materialDataMultiValue = getMultiplePropertyValue("materialData");
let materialDataTextArea = getPropertyInputElement("materialData");
let materialJson = null;
if (!materialDataMultiValue.isMultiDiffValue) {
try {
materialJson = JSON.parse(materialDataMultiValue.value);
} catch (e) {
}
}
if (materialJson !== null) {
if (materialEditor === null) {
createJSONMaterialEditor();
}
materialDataTextArea.classList.remove('multi-diff');
setMaterialEditorJSON(materialJson);
showSaveMaterialDataButton();
hideMaterialDataTextArea();
hideNewJSONMaterialEditorButton();
hideMaterialDataSaved();
} else {
// normal text
deleteJSONMaterialEditor();
materialDataTextArea.classList.toggle('multi-diff', materialDataMultiValue.isMultiDiffValue);
materialDataTextArea.value = materialDataMultiValue.isMultiDiffValue ? "" : materialDataMultiValue.value;
showMaterialDataTextArea();
showNewJSONMaterialEditorButton();
hideSaveMaterialDataButton();
hideMaterialDataSaved();
}
if (hasSelectedEntityChanged && selections.length === 1 && entityTypes[0] === "Material") {
requestMaterialTarget();
}
let activeElement = document.activeElement;
if (doSelectElement && typeof activeElement.select !== "undefined") {
activeElement.select();
}
}
}
function loaded() {
openEventBridge(function() {
let elPropertiesList = document.getElementById("properties-list");
GROUPS.forEach(function(group) {
let elGroup;
if (group.addToGroup !== undefined) {
let fieldset = document.getElementById("properties-" + group.addToGroup);
elGroup = document.createElement('div');
fieldset.appendChild(elGroup);
} else {
elGroup = document.createElement('div');
elGroup.className = 'section ' + (group.isMinor ? "minor" : "major");
elGroup.setAttribute("id", "properties-" + group.id);
elPropertiesList.appendChild(elGroup);
}
if (group.label !== undefined) {
let elLegend = document.createElement('div');
elLegend.className = "section-header";
elLegend.appendChild(createElementFromHTML(`${group.label}
`));
let elSpan = document.createElement('span');
elSpan.className = "collapse-icon";
elSpan.innerText = "M";
elLegend.appendChild(elSpan);
elGroup.appendChild(elLegend);
}
group.properties.forEach(function(propertyData) {
let propertyType = propertyData.type;
let propertyID = propertyData.propertyID;
let propertyName = propertyData.propertyName !== undefined ? propertyData.propertyName : propertyID;
let propertySpaceMode = propertyData.spaceMode !== undefined ? propertyData.spaceMode : PROPERTY_SPACE_MODE.ALL;
let propertyElementID = "property-" + propertyID;
propertyElementID = propertyElementID.replace('.', '-');
let elContainer, elLabel;
if (propertyData.replaceID === undefined) {
// Create subheader, or create new property and append it.
if (propertyType === "sub-header") {
elContainer = createElementFromHTML(
``);
} else {
elContainer = document.createElement('div');
elContainer.setAttribute("id", "div-" + propertyElementID);
elContainer.className = 'property container';
}
if (group.twoColumn && propertyData.column !== undefined && propertyData.column !== -1) {
let columnName = group.id + "column" + propertyData.column;
let elColumn = document.getElementById(columnName);
if (!elColumn) {
let columnDivName = group.id + "columnDiv";
let elColumnDiv = document.getElementById(columnDivName);
if (!elColumnDiv) {
elColumnDiv = document.createElement('div');
elColumnDiv.className = "two-column";
elColumnDiv.setAttribute("id", group.id + "columnDiv");
elGroup.appendChild(elColumnDiv);
}
elColumn = document.createElement('fieldset');
elColumn.className = "column";
elColumn.setAttribute("id", columnName);
elColumnDiv.appendChild(elColumn);
}
elColumn.appendChild(elContainer);
} else {
elGroup.appendChild(elContainer);
}
let labelText = propertyData.label !== undefined ? propertyData.label : "";
let className = '';
if (propertyData.indentedLabel || propertyData.showPropertyRule !== undefined) {
className = 'indented';
}
elLabel = createElementFromHTML(
``);
elContainer.appendChild(elLabel);
} else {
elContainer = document.getElementById(propertyData.replaceID);
}
if (elLabel) {
createAppTooltip.registerTooltipElement(elLabel.childNodes[0], propertyID, propertyName);
}
let elProperty = createElementFromHTML('');
elContainer.appendChild(elProperty);
if (propertyType === 'triple') {
elProperty.className = 'flex-row';
for (let i = 0; i < propertyData.properties.length; ++i) {
let innerPropertyData = propertyData.properties[i];
let elWrapper = createElementFromHTML('');
elProperty.appendChild(elWrapper);
let propertyID = innerPropertyData.propertyID;
let propertyName = innerPropertyData.propertyName !== undefined ? innerPropertyData.propertyName : propertyID;
let propertyElementID = "property-" + propertyID;
propertyElementID = propertyElementID.replace('.', '-');
let property = createProperty(innerPropertyData, propertyElementID, propertyName, propertyID, elWrapper);
property.isParticleProperty = group.id.includes("particles");
property.elContainer = elContainer;
property.spaceMode = propertySpaceMode;
property.group_id = group.id;
let elLabel = createElementFromHTML(`${innerPropertyData.label}
`);
createAppTooltip.registerTooltipElement(elLabel, propertyID, propertyName);
elWrapper.appendChild(elLabel);
if (property.type !== 'placeholder') {
properties[propertyID] = property;
}
if (innerPropertyData.type === 'number' || innerPropertyData.type === 'number-draggable') {
propertyRangeRequests.push(propertyID);
}
}
} else {
let property = createProperty(propertyData, propertyElementID, propertyName, propertyID, elProperty);
property.isParticleProperty = group.id.includes("particles");
property.elContainer = elContainer;
property.spaceMode = propertySpaceMode;
property.group_id = group.id;
if (property.type !== 'placeholder') {
properties[propertyID] = property;
}
if (propertyData.type === 'number' || propertyData.type === 'number-draggable' ||
propertyData.type === 'vec2' || propertyData.type === 'vec3' || propertyData.type === 'rect') {
propertyRangeRequests.push(propertyID);
}
let showPropertyRule = propertyData.showPropertyRule;
if (showPropertyRule !== undefined) {
let dependentProperty = Object.keys(showPropertyRule)[0];
let dependentPropertyValue = showPropertyRule[dependentProperty];
if (properties[dependentProperty] === undefined) {
properties[dependentProperty] = {};
}
if (properties[dependentProperty].showPropertyRules === undefined) {
properties[dependentProperty].showPropertyRules = {};
}
properties[dependentProperty].showPropertyRules[propertyID] = dependentPropertyValue;
}
}
});
elGroups[group.id] = elGroup;
});
let minorSections = document.querySelectorAll(".section.minor");
minorSections[minorSections.length - 1].className += " last";
updateVisibleSpaceModeProperties();
if (window.EventBridge !== undefined) {
EventBridge.scriptEventReceived.connect(function(data) {
data = JSON.parse(data);
if (data.type === "server_script_status" && selectedEntityIDs.size === 1) {
let elServerScriptError = document.getElementById("property-serverScripts-error");
let elServerScriptStatus = document.getElementById("property-serverScripts-status");
elServerScriptError.value = data.errorInfo;
// If we just set elServerScriptError's display to block or none, we still end up with
// it's parent contributing 21px bottom padding even when elServerScriptError is display:none.
// So set it's parent to block or none
elServerScriptError.parentElement.style.display = data.errorInfo ? "block" : "none";
if (data.statusRetrieved === false) {
elServerScriptStatus.innerText = "Failed to retrieve status";
} else if (data.isRunning) {
elServerScriptStatus.innerText = ENTITY_SCRIPT_STATUS[data.status] || data.status;
} else {
elServerScriptStatus.innerText = NOT_RUNNING_SCRIPT_STATUS;
}
} else if (data.type === "update" && data.selections) {
if (data.spaceMode !== undefined) {
currentSpaceMode = data.spaceMode === "local" ? PROPERTY_SPACE_MODE.LOCAL : PROPERTY_SPACE_MODE.WORLD;
}
handleEntitySelectionUpdate(data.selections, data.isPropertiesToolUpdate);
} else if (data.type === 'tooltipsReply') {
createAppTooltip.setIsEnabled(!data.hmdActive);
createAppTooltip.setTooltipData(data.tooltips);
} else if (data.type === 'hmdActiveChanged') {
createAppTooltip.setIsEnabled(!data.hmdActive);
} else if (data.type === 'setSpaceMode') {
currentSpaceMode = data.spaceMode === "local" ? PROPERTY_SPACE_MODE.LOCAL : PROPERTY_SPACE_MODE.WORLD;
updateVisibleSpaceModeProperties();
} else if (data.type === 'propertyRangeReply') {
let propertyRanges = data.propertyRanges;
for (let property in propertyRanges) {
let propertyRange = propertyRanges[property];
if (propertyRange !== undefined) {
let propertyData = properties[property].data;
let multiplier = propertyData.multiplier;
if (propertyData.min === undefined && propertyRange.minimum !== "") {
propertyData.min = propertyRange.minimum;
if (multiplier !== undefined) {
propertyData.min /= multiplier;
}
}
if (propertyData.max === undefined && propertyRange.maximum !== "") {
propertyData.max = propertyRange.maximum;
if (multiplier !== undefined) {
propertyData.max /= multiplier;
}
}
switch (propertyData.type) {
case 'number':
updateNumberMinMax(properties[property]);
break;
case 'number-draggable':
updateNumberDraggableMinMax(properties[property]);
break;
case 'vec3':
case 'vec2':
updateVectorMinMax(properties[property]);
break;
case 'rect':
updateRectMinMax(properties[property]);
break;
}
}
}
} else if (data.type === 'materialTargetReply') {
if (data.entityID === getFirstSelectedID()) {
setMaterialTargetData(data.materialTargetData);
}
}
});
// Request tooltips and property ranges as soon as we can process a reply:
EventBridge.emitWebEvent(JSON.stringify({ type: 'tooltipsRequest' }));
EventBridge.emitWebEvent(JSON.stringify({ type: 'propertyRangeRequest', properties: propertyRangeRequests }));
}
// Server Script Status
let elServerScriptStatusOuter = document.getElementById('div-property-serverScriptStatus');
let elServerScriptStatusContainer = document.getElementById('div-property-serverScriptStatus').childNodes[1];
let serverScriptStatusElementID = 'property-serverScripts-status';
createAppTooltip.registerTooltipElement(elServerScriptStatusOuter.childNodes[0], "serverScriptsStatus");
let elServerScriptStatus = document.createElement('div');
elServerScriptStatus.setAttribute("id", serverScriptStatusElementID);
elServerScriptStatusContainer.appendChild(elServerScriptStatus);
// Server Script Error
let elServerScripts = getPropertyInputElement("serverScripts");
let elDiv = document.createElement('div');
elDiv.className = "property";
let elServerScriptError = document.createElement('textarea');
let serverScriptErrorElementID = 'property-serverScripts-error';
elServerScriptError.setAttribute("id", serverScriptErrorElementID);
elDiv.appendChild(elServerScriptError);
elServerScriptStatusContainer.appendChild(elDiv);
let elScript = getPropertyInputElement("script");
elScript.parentNode.className = "url refresh";
elServerScripts.parentNode.className = "url refresh";
// User Data
let userDataProperty = properties["userData"];
let elUserData = userDataProperty.elInput;
let userDataElementID = userDataProperty.elementID;
elDiv = elUserData.parentNode;
let elStaticUserData = document.createElement('div');
elStaticUserData.setAttribute("id", userDataElementID + "-static");
let elUserDataEditor = document.createElement('div');
elUserDataEditor.setAttribute("id", userDataElementID + "-editor");
let elUserDataEditorStatus = document.createElement('div');
elUserDataEditorStatus.setAttribute("id", userDataElementID + "-editorStatus");
let elUserDataSaved = document.createElement('span');
elUserDataSaved.setAttribute("id", userDataElementID + "-saved");
elUserDataSaved.innerText = "Saved!";
elDiv.childNodes[JSON_EDITOR_ROW_DIV_INDEX].appendChild(elUserDataSaved);
elDiv.insertBefore(elStaticUserData, elUserData);
elDiv.insertBefore(elUserDataEditor, elUserData);
elDiv.insertBefore(elUserDataEditorStatus, elUserData);
// Material Data
let materialDataProperty = properties["materialData"];
let elMaterialData = materialDataProperty.elInput;
let materialDataElementID = materialDataProperty.elementID;
elDiv = elMaterialData.parentNode;
let elStaticMaterialData = document.createElement('div');
elStaticMaterialData.setAttribute("id", materialDataElementID + "-static");
let elMaterialDataEditor = document.createElement('div');
elMaterialDataEditor.setAttribute("id", materialDataElementID + "-editor");
let elMaterialDataEditorStatus = document.createElement('div');
elMaterialDataEditorStatus.setAttribute("id", materialDataElementID + "-editorStatus");
let elMaterialDataSaved = document.createElement('span');
elMaterialDataSaved.setAttribute("id", materialDataElementID + "-saved");
elMaterialDataSaved.innerText = "Saved!";
elDiv.childNodes[JSON_EDITOR_ROW_DIV_INDEX].appendChild(elMaterialDataSaved);
elDiv.insertBefore(elStaticMaterialData, elMaterialData);
elDiv.insertBefore(elMaterialDataEditor, elMaterialData);
elDiv.insertBefore(elMaterialDataEditorStatus, elMaterialData);
// Collapsible sections
let elCollapsible = document.getElementsByClassName("collapse-icon");
let toggleCollapsedEvent = function(event) {
let element = this.parentNode.parentNode;
let isCollapsed = element.dataset.collapsed !== "true";
element.dataset.collapsed = isCollapsed ? "true" : false;
element.setAttribute("collapsed", isCollapsed ? "true" : "false");
this.textContent = isCollapsed ? "L" : "M";
};
for (let collapseIndex = 0, numCollapsibles = elCollapsible.length; collapseIndex < numCollapsibles; ++collapseIndex) {
let curCollapsibleElement = elCollapsible[collapseIndex];
curCollapsibleElement.addEventListener("click", toggleCollapsedEvent, true);
}
// Textarea scrollbars
let elTextareas = document.getElementsByTagName("TEXTAREA");
let textareaOnChangeEvent = function(event) {
setTextareaScrolling(event.target);
};
for (let textAreaIndex = 0, numTextAreas = elTextareas.length; textAreaIndex < numTextAreas; ++textAreaIndex) {
let curTextAreaElement = elTextareas[textAreaIndex];
setTextareaScrolling(curTextAreaElement);
curTextAreaElement.addEventListener("input", textareaOnChangeEvent, false);
curTextAreaElement.addEventListener("change", textareaOnChangeEvent, false);
/* FIXME: Detect and update textarea scrolling attribute on resize. Unfortunately textarea doesn't have a resize
event; mouseup is a partial stand-in but doesn't handle resizing if mouse moves outside textarea rectangle. */
curTextAreaElement.addEventListener("mouseup", textareaOnChangeEvent, false);
}
// Dropdowns
// For each dropdown the following replacement is created in place of the original dropdown...
// Structure created:
//
// - display textcarat
// -
//
// - 0) {
let el = elDropdowns[0];
el.parentNode.removeChild(el);
elDropdowns = document.getElementsByTagName("select");
}
const KEY_CODES = {
BACKSPACE: 8,
DELETE: 46
};
document.addEventListener("keyup", function (keyUpEvent) {
const FILTERED_NODE_NAMES = ["INPUT", "TEXTAREA"];
if (FILTERED_NODE_NAMES.includes(keyUpEvent.target.nodeName)) {
return;
}
if (elUserDataEditor.contains(keyUpEvent.target) || elMaterialDataEditor.contains(keyUpEvent.target)) {
return;
}
let {code, key, keyCode, altKey, ctrlKey, metaKey, shiftKey} = keyUpEvent;
let controlKey = window.navigator.platform.startsWith("Mac") ? metaKey : ctrlKey;
let keyCodeString;
switch (keyCode) {
case KEY_CODES.DELETE:
keyCodeString = "Delete";
break;
case KEY_CODES.BACKSPACE:
keyCodeString = "Backspace";
break;
default:
keyCodeString = String.fromCharCode(keyUpEvent.keyCode);
break;
}
EventBridge.emitWebEvent(JSON.stringify({
type: 'keyUpEvent',
keyUpEvent: {
code,
key,
keyCode,
keyCodeString,
altKey,
controlKey,
shiftKey,
}
}));
}, false);
window.onblur = function() {
// Fake a change event
let ev = document.createEvent("HTMLEvents");
ev.initEvent("change", true, true);
document.activeElement.dispatchEvent(ev);
};
// For input and textarea elements, select all of the text on focus
let els = document.querySelectorAll("input, textarea");
for (let i = 0; i < els.length; ++i) {
els[i].onfocus = function (e) {
e.target.select();
};
}
bindAllNonJSONEditorElements();
showGroupsForType("None");
resetProperties();
disableProperties();
});
augmentSpinButtons();
disableDragDrop();
// Disable right-click context menu which is not visible in the HMD and makes it seem like the app has locked
document.addEventListener("contextmenu", function(event) {
event.preventDefault();
}, false);
setTimeout(function() {
EventBridge.emitWebEvent(JSON.stringify({ type: 'propertiesPageReady' }));
}, 1000);
}