diff --git a/scripts/system/create/assets/data/createAppTooltips.json b/scripts/system/create/assets/data/createAppTooltips.json
index 7c197e45df..62465573a4 100644
--- a/scripts/system/create/assets/data/createAppTooltips.json
+++ b/scripts/system/create/assets/data/createAppTooltips.json
@@ -698,6 +698,9 @@
"renderWithZones": {
"tooltip": "If set, this entity will only render when your avatar is inside of a zone in this list."
},
+ "tags": {
+ "tooltip": "A set of tags describing this entity that can be used with the API."
+ },
"groupCulled": {
"tooltip": "If false, individual pieces of the entity may be culled by the render engine. If true, either the entire entity will be culled, or it won't at all."
},
diff --git a/scripts/system/create/entityProperties/html/js/entityProperties.js b/scripts/system/create/entityProperties/html/js/entityProperties.js
index 458ebc1b7c..c8fe04fbbe 100644
--- a/scripts/system/create/entityProperties/html/js/entityProperties.js
+++ b/scripts/system/create/entityProperties/html/js/entityProperties.js
@@ -23,6 +23,8 @@ const ENTITY_HOST_TYPE_COLOR_LOCAL = "#f0d769";
const NO_SELECTION = ",";
+const MAX_TAGS_PER_ROWS = 5;
+
const PROPERTY_SPACE_MODE = Object.freeze({
ALL: 0,
LOCAL: 1,
@@ -154,7 +156,13 @@ const GROUPS = [
label: "Render With Zones",
type: "multipleZonesSelection",
propertyID: "renderWithZones",
- }
+ },
+ {
+ label: "Tags",
+ type: "arrayOfStrings",
+ propertyID: "tags",
+ useStringColor: true,
+ },
]
},
{
@@ -1126,7 +1134,7 @@ const GROUPS = [
vec2Type: "xyz",
min: 0,
max: 1,
- step: 0.1,
+ step: 0.005,
decimals: 4,
subLabels: [ "x", "y" ],
propertyID: "materialMappingPos",
@@ -1135,7 +1143,7 @@ const GROUPS = [
label: "Material Scale",
type: "vec2",
vec2Type: "xyz",
- step: 0.1,
+ step: 0.005,
decimals: 4,
subLabels: [ "x", "y" ],
propertyID: "materialMappingScale",
@@ -2297,6 +2305,8 @@ function getPropertyInputElement(propertyID) {
return property.elInput;
case 'multipleZonesSelection':
return property.elInput;
+ case 'arrayOfStrings':
+ return property.elInput;
case 'number-draggable':
return property.elNumber.elInput;
case 'rect':
@@ -2464,6 +2474,12 @@ function resetProperties() {
setZonesSelectionData(property.elInput, false);
break;
}
+ case 'arrayOfStrings': {
+ property.elInput.classList.remove('multi-diff');
+ property.elInput.value = "[]";
+ setArrayOfStringsUi(property.elInput.id, false);
+ break;
+ }
case 'childList': {
setChildListData(property.elInput, undefined, "");
break;
@@ -3716,6 +3732,10 @@ function createProperty(propertyData, propertyElementID, propertyName, propertyI
property.elInput = createZonesSelection(property, elProperty);
break;
}
+ case 'arrayOfStrings': {
+ property.elInput = createArrayOfStrings(property, elProperty);
+ break;
+ }
case 'childList': {
property.elInput = createChildList(property, elProperty);
break;
@@ -4834,6 +4854,155 @@ function setZonesSelectionData(element, isEditable) {
displaySelectedZones(element.id, isEditable);
}
+/**
+ * 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
@@ -5530,7 +5699,7 @@ function handleEntitySelectionUpdate(selections, isPropertiesToolUpdate) {
break;
}
case 'multipleZonesSelection': {
- property.elInput.value = JSON.stringify(propertyValue);
+ property.elInput.value = JSON.stringify(propertyValue);
if (lockedMultiValue.isMultiDiffValue || lockedMultiValue.value) {
setZonesSelectionData(property.elInput, false);
} else {
@@ -5538,6 +5707,15 @@ function handleEntitySelectionUpdate(selections, isPropertiesToolUpdate) {
}
break;
}
+ case 'arrayOfStrings': {
+ property.elInput.value = JSON.stringify(propertyValue);
+ if (lockedMultiValue.isMultiDiffValue || lockedMultiValue.value) {
+ setArrayOfStringsUi(property.elInput.id, false);
+ } else {
+ setArrayOfStringsUi(property.elInput.id, true);
+ }
+ break;
+ }
case 'childList': {
let parentID = selections[0].properties.parentID;
if (selections.length !== 1 || parentID === UUID_NONE) {
diff --git a/scripts/system/html/css/edit-style.css b/scripts/system/html/css/edit-style.css
index 7658e2fb24..c6ff017a42 100644
--- a/scripts/system/html/css/edit-style.css
+++ b/scripts/system/html/css/edit-style.css
@@ -335,6 +335,10 @@ input[type="text"] {
width: 100%;
}
+input.arrayOfStringsStringToAdd {
+ width: 70%;
+}
+
input.multi-diff:not(:focus) + span.multi-diff,
textarea.multi-diff:not(:focus) + span.multi-diff,
.draggable-number.multi-diff>input:not(:focus)+span.multi-diff,
@@ -2292,3 +2296,35 @@ font.viewParentIcon {
font-weight: bold;
}
+div.arrayOfStringsContainer {
+ width: 100%;
+ overflow: hidden;
+}
+
+div.arrayOfStringsAddComponentContainer {
+ margin-top: 2px;
+}
+
+div.arrayOfStringsTags {
+ color: #000000;
+ font-family: FiraSans-SemiBold;
+ font-size: 12px;
+ border-radius: 10px;
+ padding: 5px 8px 5px 8px;
+ margin: 2px;
+ text-align: left;
+ background-color: #dddddd;
+ border: none;
+ display: inline-block;
+ width: auto;
+ max-width: none;
+}
+
+span.arrayOfStringsTagsRemove {
+ color: #777777;
+}
+
+span.arrayOfStringsTagsRemove:hover {
+ color: #000000;
+}
+