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; +} +