From c4a22d690b31e50ef6acbfc5af784769f8cdd820 Mon Sep 17 00:00:00 2001 From: tariqksoliman Date: Thu, 6 Mar 2025 18:43:45 -0800 Subject: [PATCH 1/2] #643 InfoTool with one-to-many Datasets --- .../src/metaconfigs/layer-model-config.json | 7 + .../src/metaconfigs/layer-query-config.json | 7 + .../src/metaconfigs/layer-vector-config.json | 7 + .../metaconfigs/layer-vectortile-config.json | 7 + src/essence/Ancillary/Datasets.js | 62 +++ src/essence/Basics/Layers_/Layers_.js | 2 + src/essence/Basics/Map_/Map_.js | 355 ++++++++---------- src/essence/Tools/Info/InfoTool.css | 13 + src/essence/Tools/Info/InfoTool.js | 77 +++- src/essence/Tools/Kinds/Kinds.js | 1 + 10 files changed, 338 insertions(+), 200 deletions(-) create mode 100644 src/essence/Ancillary/Datasets.js diff --git a/configure/src/metaconfigs/layer-model-config.json b/configure/src/metaconfigs/layer-model-config.json index 717e07f59..ead185103 100644 --- a/configure/src/metaconfigs/layer-model-config.json +++ b/configure/src/metaconfigs/layer-model-config.json @@ -835,6 +835,13 @@ "description": "This is a property key already within the features properties. It's value will be searched for in the specified dataset column.", "type": "text", "width": 3 + }, + { + "field": "displayProp", + "name": "Connecting Feature Display Name Property", + "description": "This is a property key within the returned dataset feature properties. This is effectively an optional second-order property key. In the case of multiple dataset row entries being returned, this is useful for users of the UI to be able to distinguish between them. If unset, defaults to the value of 'Connecting Feature Property'.", + "type": "text", + "width": 3 } ] } diff --git a/configure/src/metaconfigs/layer-query-config.json b/configure/src/metaconfigs/layer-query-config.json index bdb203a0c..2f43290ed 100644 --- a/configure/src/metaconfigs/layer-query-config.json +++ b/configure/src/metaconfigs/layer-query-config.json @@ -984,6 +984,13 @@ "description": "This is a property key already within the features properties. It's value will be searched for in the specified dataset column.", "type": "text", "width": 3 + }, + { + "field": "displayProp", + "name": "Connecting Feature Display Name Property", + "description": "This is a property key within the returned dataset feature properties. This is effectively an optional second-order property key. In the case of multiple dataset row entries being returned, this is useful for users of the UI to be able to distinguish between them. If unset, defaults to the value of 'Connecting Feature Property'.", + "type": "text", + "width": 3 } ] } diff --git a/configure/src/metaconfigs/layer-vector-config.json b/configure/src/metaconfigs/layer-vector-config.json index e7522b1af..02eb19b99 100644 --- a/configure/src/metaconfigs/layer-vector-config.json +++ b/configure/src/metaconfigs/layer-vector-config.json @@ -931,6 +931,13 @@ "description": "This is a property key already within the features properties. It's value will be searched for in the specified dataset column.", "type": "text", "width": 3 + }, + { + "field": "displayProp", + "name": "Connecting Feature Display Name Property", + "description": "This is a property key within the returned dataset feature properties. This is effectively an optional second-order property key. In the case of multiple dataset row entries being returned, this is useful for users of the UI to be able to distinguish between them. If unset, defaults to the value of 'Connecting Feature Property'.", + "type": "text", + "width": 3 } ] } diff --git a/configure/src/metaconfigs/layer-vectortile-config.json b/configure/src/metaconfigs/layer-vectortile-config.json index 130cdced3..bef1c294a 100644 --- a/configure/src/metaconfigs/layer-vectortile-config.json +++ b/configure/src/metaconfigs/layer-vectortile-config.json @@ -892,6 +892,13 @@ "description": "This is a property key already within the features properties. It's value will be searched for in the specified dataset column.", "type": "text", "width": 3 + }, + { + "field": "displayProp", + "name": "Connecting Feature Display Name Property", + "description": "This is a property key within the returned dataset feature properties. This is effectively an optional second-order property key. In the case of multiple dataset row entries being returned, this is useful for users of the UI to be able to distinguish between them. If unset, defaults to the value of 'Connecting Feature Property'.", + "type": "text", + "width": 3 } ] } diff --git a/src/essence/Ancillary/Datasets.js b/src/essence/Ancillary/Datasets.js new file mode 100644 index 000000000..54adc29a1 --- /dev/null +++ b/src/essence/Ancillary/Datasets.js @@ -0,0 +1,62 @@ +import F_ from '../Basics/Formulae_/Formulae_' +import calls from '../../pre/calls' + +const Datasets = { + populateFromDataset(layer, cb) { + if ( + layer.options.layerName && + L_.layers.data[layer.options.layerName] && + L_.layers.data[layer.options.layerName].variables && + L_.layers.data[layer.options.layerName].variables.datasetLinks + ) { + const dl = + L_.layers.data[layer.options.layerName].variables.datasetLinks + let dlFilled = dl + for (let i = 0; i < dlFilled.length; i++) { + dlFilled[i].search = F_.getIn( + layer.feature.properties, + dlFilled[i].prop.split('.') + ) + } + + calls.api( + 'datasets_get', + { + queries: JSON.stringify(dlFilled), + }, + function (data) { + const d = data.body + for (let i = 0; i < d.length; i++) { + if (d[i].type == 'images') { + layer.feature.properties.images = + layer.feature.properties.images || [] + for (let j = 0; j < d[i].results.length; j++) { + layer.feature.properties.images.push( + d[i].results[j] + ) + } + //remove duplicates + layer.feature.properties.images = + F_.removeDuplicatesInArrayOfObjects( + layer.feature.properties.images + ) + } else { + layer.feature.properties._dataset = { + prop: dlFilled[i].displayProp || d[i].prop, + results: d[i].results, + } + } + } + if (cb != null && typeof cb === 'function') cb() + }, + function (data) { + if (cb != null && typeof cb === 'function') cb() + } + ) + } else { + if (cb != null && typeof cb === 'function') cb() + } + }, +} + +export default Datasets diff --git a/src/essence/Basics/Layers_/Layers_.js b/src/essence/Basics/Layers_/Layers_.js index a9d0db491..94fbdb685 100644 --- a/src/essence/Basics/Layers_/Layers_.js +++ b/src/essence/Basics/Layers_/Layers_.js @@ -2054,6 +2054,8 @@ const L_ = { const featureWithout_ = JSON.parse(JSON.stringify(f)) if (featureWithout_.properties?._ != null) delete featureWithout_.properties._ + if (featureWithout_.properties?._dataset != null) + delete featureWithout_.properties._dataset for (let i = 0; i < layerKeys.length; i++) { const l = layerKeys[i] diff --git a/src/essence/Basics/Map_/Map_.js b/src/essence/Basics/Map_/Map_.js index 2f739f020..241c8f75a 100644 --- a/src/essence/Basics/Map_/Map_.js +++ b/src/essence/Basics/Map_/Map_.js @@ -14,6 +14,7 @@ import ToolController_ from '../ToolController_/ToolController_' import CursorInfo from '../../Ancillary/CursorInfo' import Description from '../../Ancillary/Description' import QueryURL from '../../Ancillary/QueryURL' +import Datasets from '../../Ancillary/Datasets' import { Kinds } from '../../../pre/tools' import DataShaders from '../../Ancillary/DataShaders' import calls from '../../../pre/calls' @@ -415,7 +416,9 @@ let Map_ = { Map_.map.addLayer(L_.layers.layer[L_._layersOrdered[hasIndex[i]]]) // If image layer, reorder the z index and redraw the layer - if (L_.layers.data[L_._layersOrdered[hasIndex[i]]].type === 'image') { + if ( + L_.layers.data[L_._layersOrdered[hasIndex[i]]].type === 'image' + ) { L_.layers.layer[L_._layersOrdered[hasIndex[i]]].setZIndex( L_._layersOrdered.length + 1 - @@ -730,60 +733,7 @@ function featureDefaultClick(feature, layer, e) { ToolController_.activeTool.disableLayerInteractions === true ) return - - //Query dataset links if possible and add that data to the feature's properties - if ( - layer.options.layerName && - L_.layers.data[layer.options.layerName] && - L_.layers.data[layer.options.layerName].variables && - L_.layers.data[layer.options.layerName].variables.datasetLinks - ) { - const dl = - L_.layers.data[layer.options.layerName].variables.datasetLinks - let dlFilled = dl - for (let i = 0; i < dlFilled.length; i++) { - dlFilled[i].search = F_.getIn( - layer.feature.properties, - dlFilled[i].prop.split('.') - ) - } - - calls.api( - 'datasets_get', - { - queries: JSON.stringify(dlFilled), - }, - function (data) { - const d = data.body - for (let i = 0; i < d.length; i++) { - if (d[i].type == 'images') { - layer.feature.properties.images = - layer.feature.properties.images || [] - for (let j = 0; j < d[i].results.length; j++) { - layer.feature.properties.images.push( - d[i].results[j] - ) - } - //remove duplicates - layer.feature.properties.images = - F_.removeDuplicatesInArrayOfObjects( - layer.feature.properties.images - ) - } else { - layer.feature.properties._data = d[i].results - } - } - keepGoing() - }, - function (data) { - keepGoing() - } - ) - } else { - keepGoing() - } - - function keepGoing() { + Datasets.populateFromDataset(layer, () => { Kinds.use( L_.layers.data[layer.options.layerName].kind, Map_, @@ -852,7 +802,7 @@ function featureDefaultClick(feature, layer, e) { } QueryURL.writeSearchURL([searchStr], layer.options.layerName) - } + }) } //Pretty much like makePointLayer but without the pointToLayer stuff @@ -1274,27 +1224,28 @@ function makeVectorTileLayer(layerObj) { let ell = { latlng: null } if (e.latlng != null) ell.latlng = JSON.parse(JSON.stringify(e.latlng)) + Datasets.populateFromDataset(layer, () => { + Kinds.use( + L_.layers.data[layerName].kind, + Map_, + L_.layers.layer[layerName].activeFeatures[0], + layer, + layerName, + null, + ell + ) - Kinds.use( - L_.layers.data[layerName].kind, - Map_, - L_.layers.layer[layerName].activeFeatures[0], - layer, - layerName, - null, - ell - ) - - ToolController_.getTool('InfoTool').use( - layer, - layerName, - L_.layers.layer[layerName].activeFeatures, - null, - null, - null, - ell - ) - L_.layers.layer[layerName].activeFeatures = [] + ToolController_.getTool('InfoTool').use( + layer, + layerName, + L_.layers.layer[layerName].activeFeatures, + null, + null, + null, + ell + ) + L_.layers.layer[layerName].activeFeatures = [] + }) } })(layer, layerName, e), 100 @@ -1469,142 +1420,166 @@ function makeImageLayer(layerObj) { ) } - const cogColormap = F_.getIn( - L_.layers.data[layerObj.name], - 'cogColormap' - ) + const cogColormap = F_.getIn(L_.layers.data[layerObj.name], 'cogColormap') - parseGeoraster(layerUrl).then((georaster) => { - let pixelValuesToColorFn = null; - if (F_.getIn( - L_.layers.data[layerObj.name], - 'variables.hideNoDataValue' - ) === true) { - pixelValuesToColorFn = (values) => { - // https://github.com/GeoTIFF/georaster-layer-for-leaflet/issues/16 - return values[0] === georaster.noDataValue ? null : `rgb(${values[0]},${values[1]},${values[2]})` + parseGeoraster(layerUrl) + .then((georaster) => { + let pixelValuesToColorFn = null + if ( + F_.getIn( + L_.layers.data[layerObj.name], + 'variables.hideNoDataValue' + ) === true + ) { + pixelValuesToColorFn = (values) => { + // https://github.com/GeoTIFF/georaster-layer-for-leaflet/issues/16 + return values[0] === georaster.noDataValue + ? null + : `rgb(${values[0]},${values[1]},${values[2]})` + } } - } - const imageInfo = F_.getIn( - L_.layers.data[layerObj.name], - 'variables.image' - ) - - let min = null - let max = null - if (georaster.numberOfRasters === 1) { - min = layerObj.cogMin - max = layerObj.cogMax - - if (isNaN(parseFloat(layerObj.cogMin)) || isNaN(parseFloat(layerObj.cogMax))) { - let path - if (layerObj.url.startsWith('http')) path = layerObj.url - else path = 'Missions/' + L_.mission + '/' + layerObj.url - - // Try to get the min and max values using gdal if the user did not input min/max in the layer config - $.ajax({ - type: calls.getminmax.type, - url: calls.getminmax.url, - data: { - type: 'minmax', - path: calls.getprofile.pathprefix + path, - bands: '[1]', // Assume the geotiff images only have a single band - }, - async: false, - success: function (data) { - if (data && data[0] && data[0].band && data[0].band === 1) { - if (isNaN(parseFloat(layerObj.cogMin))) { - min = data[0].min - layerObj.cogMin = min - } - if (isNaN(parseFloat(layerObj.cogMax))) { - max = data[0].max - layerObj.cogMax = max - } - } - }, - error: function (request, status, error) { - console.warn(`Failed to get gdal minmax info for ${layerObj.name}`, request, status, error) - }, - }) + const imageInfo = F_.getIn( + L_.layers.data[layerObj.name], + 'variables.image' + ) - } + let min = null + let max = null + if (georaster.numberOfRasters === 1) { + min = layerObj.cogMin + max = layerObj.cogMax - // FIXME A lot of this code is duplicated in LayersTool so find some way to consolidate them as functions - var range = max - min - let colormap = null - let reverse = false - if (layerObj.cogTransform === true && 'cogColormap' in layerObj) { - colormap = layerObj.cogColormap - // TiTiler colormap variables are all lower case so we need to format them correctly for js-colormaps - if (colormap.toLowerCase().endsWith('_r')) { - colormap = colormap.substring(0, colormap.length - 2) - reverse = true + if ( + isNaN(parseFloat(layerObj.cogMin)) || + isNaN(parseFloat(layerObj.cogMax)) + ) { + let path + if (layerObj.url.startsWith('http')) path = layerObj.url + else path = 'Missions/' + L_.mission + '/' + layerObj.url + + // Try to get the min and max values using gdal if the user did not input min/max in the layer config + $.ajax({ + type: calls.getminmax.type, + url: calls.getminmax.url, + data: { + type: 'minmax', + path: calls.getprofile.pathprefix + path, + bands: '[1]', // Assume the geotiff images only have a single band + }, + async: false, + success: function (data) { + if ( + data && + data[0] && + data[0].band && + data[0].band === 1 + ) { + if (isNaN(parseFloat(layerObj.cogMin))) { + min = data[0].min + layerObj.cogMin = min + } + if (isNaN(parseFloat(layerObj.cogMax))) { + max = data[0].max + layerObj.cogMax = max + } + } + }, + error: function (request, status, error) { + console.warn( + `Failed to get gdal minmax info for ${layerObj.name}`, + request, + status, + error + ) + }, + }) } - let index = Object.keys(colormapData).findIndex(v => { - return v.toLowerCase() === colormap.toLowerCase(); - }); + // FIXME A lot of this code is duplicated in LayersTool so find some way to consolidate them as functions + var range = max - min + let colormap = null + let reverse = false + if ( + layerObj.cogTransform === true && + 'cogColormap' in layerObj + ) { + colormap = layerObj.cogColormap + // TiTiler colormap variables are all lower case so we need to format them correctly for js-colormaps + if (colormap.toLowerCase().endsWith('_r')) { + colormap = colormap.substring(0, colormap.length - 2) + reverse = true + } - if (index > -1) { - colormap = Object.keys(colormapData)[index] + let index = Object.keys(colormapData).findIndex((v) => { + return v.toLowerCase() === colormap.toLowerCase() + }) + + if (index > -1) { + colormap = Object.keys(colormapData)[index] + } else { + colormap = 'binary' // Give it the default value + } } else { colormap = 'binary' // Give it the default value } - } else { - colormap = 'binary' // Give it the default value - } - pixelValuesToColorFn = (values) => { - var pixelValue = values[0]; // single band - // don't return a color - if (georaster.noDataValue && georaster.noDataValue === pixelValue) { - return null; - } + pixelValuesToColorFn = (values) => { + var pixelValue = values[0] // single band + // don't return a color + if ( + georaster.noDataValue && + georaster.noDataValue === pixelValue + ) { + return null + } - // scale from 0 - 1 - var scaledPixelValue = (pixelValue - min) / range; - if (!(scaledPixelValue >= 0 && scaledPixelValue <= 1)) { - if (imageInfo && imageInfo.fillMinMax) { - if (scaledPixelValue <= 0) { - scaledPixelValue = 0 - } else if (scaledPixelValue >= 1.0) { - scaledPixelValue = 1 + // scale from 0 - 1 + var scaledPixelValue = (pixelValue - min) / range + if (!(scaledPixelValue >= 0 && scaledPixelValue <= 1)) { + if (imageInfo && imageInfo.fillMinMax) { + if (scaledPixelValue <= 0) { + scaledPixelValue = 0 + } else if (scaledPixelValue >= 1.0) { + scaledPixelValue = 1 + } + } else { + return null } - } else { - return null } - } - return evaluate_cmap(scaledPixelValue, colormap || IMAGE_DEFAULT_COLOR_RAMP, reverse) + return evaluate_cmap( + scaledPixelValue, + colormap || IMAGE_DEFAULT_COLOR_RAMP, + reverse + ) + } } - } - L_.layers.layer[layerObj.name] = new GeoRasterLayer({ - georaster: georaster, - resolution: 256, - opacity: 1.0, - pixelValuesToColorFn: pixelValuesToColorFn, - }) + L_.layers.layer[layerObj.name] = new GeoRasterLayer({ + georaster: georaster, + resolution: 256, + opacity: 1.0, + pixelValuesToColorFn: pixelValuesToColorFn, + }) - L_.layers.layer[layerObj.name].clearCache() + L_.layers.layer[layerObj.name].clearCache() - L_.layers.layer[layerObj.name].setZIndex( - L_._layersOrdered.length + - 1 - - L_._layersOrdered.indexOf(layerObj.name) - ) + L_.layers.layer[layerObj.name].setZIndex( + L_._layersOrdered.length + + 1 - + L_._layersOrdered.indexOf(layerObj.name) + ) - L_.setLayerOpacity(layerObj.name, L_.layers.opacity[layerObj.name]) + L_.setLayerOpacity(layerObj.name, L_.layers.opacity[layerObj.name]) - L_._layersLoaded[L_._layersOrdered.indexOf(layerObj.name)] = true - allLayersLoaded() - }) - .catch((e) => { - console.warn('Unable to load image') - return null - }); + L_._layersLoaded[L_._layersOrdered.indexOf(layerObj.name)] = true + allLayersLoaded() + }) + .catch((e) => { + console.warn('Unable to load image') + return null + }) } //Because some layers load faster than others, check to see if diff --git a/src/essence/Tools/Info/InfoTool.css b/src/essence/Tools/Info/InfoTool.css index c45547aa3..dbf61789a 100644 --- a/src/essence/Tools/Info/InfoTool.css +++ b/src/essence/Tools/Info/InfoTool.css @@ -115,6 +115,19 @@ border-top: 1px solid var(--color-a-5); } +#infoToolSelectedDatasetDropdown { + width: 100%; + height: 40px; + background: var(--color-a1-5); + border-bottom: 1px solid var(--color-a-5); +} + +#infoToolSelectedDatasetDropdown .dropy__title span { + color: var(--color-h); + font-size: 14px; + padding-top: 12px; +} + #infoToolFilter { display: flex; height: 36px; diff --git a/src/essence/Tools/Info/InfoTool.js b/src/essence/Tools/Info/InfoTool.js index 63a659ff5..c90369cd9 100644 --- a/src/essence/Tools/Info/InfoTool.js +++ b/src/essence/Tools/Info/InfoTool.js @@ -6,9 +6,12 @@ import Map_ from '../../Basics/Map_/Map_' import { Kinds } from '../../../pre/tools' import Dropy from '../../../external/Dropy/dropy' +import Datasets from '../../Ancillary/Datasets' import Help from '../../Ancillary/Help' import ConfirmationModal from '../../Ancillary/ConfirmationModal' +import tippy from 'tippy.js' + import './InfoTool.css' const helpKey = 'InfoTool' @@ -48,6 +51,9 @@ var markup = [ "", "", "", + "
", + "
", + "
", "
", "", "
No feature selected
", @@ -63,6 +69,7 @@ var InfoTool = { info: null, variables: null, activeFeatureI: null, + activeDatasetI: null, featureLayers: [], filterString: '', hiddenShown: false, @@ -118,6 +125,7 @@ var InfoTool = { activeIndex = foundI != -1 ? foundI : 0 } this.activeFeatureI = activeIndex + this.activeDatasetI = 0 this.initialEvent = initialEvent // Always highlight even if redundant @@ -141,6 +149,17 @@ var InfoTool = { //Add the markup to tools or do it manually tools.html(markup) + tippy('#infoToolSelected', { + content: 'Select An Overlapping Feature', + placement: 'right', + theme: 'blue', + }) + tippy('#infoToolSelectedDataset', { + content: 'Select An Associated Dataset', + placement: 'right', + theme: 'blue', + }) + Help.finalize(helpKey) $('#infoToolUnhideAll').css( @@ -151,6 +170,7 @@ var InfoTool = { if (this.info == null || this.info.length == 0) { $('#infoToolHeader > .right').css('display', 'none') $('#infoToolSelected').css('display', 'none') + $('#infoToolSelectedDataset').css('display', 'none') $('#infoToolFilter').css('display', 'none') $('#infoToolNoneSelected').css('display', 'block') return @@ -219,17 +239,24 @@ var InfoTool = { ) Dropy.init($('#infoToolSelectedDropdown'), function (idx) { let e = JSON.parse(JSON.stringify(InfoTool.initialEvent)) - Kinds.use( - L_.layers.data[InfoTool.currentLayerName]?.kind || null, - Map_, - InfoTool.info[idx], + Datasets.populateFromDataset( InfoTool.featureLayers[idx] || InfoTool.currentLayer, - InfoTool.currentLayerName, - null, - e, - { idx: idx }, - InfoTool.info, - InfoTool.featureLayers[idx] ? InfoTool.featureLayers : null + () => { + Kinds.use( + L_.layers.data[InfoTool.currentLayerName]?.kind || null, + Map_, + InfoTool.info[idx], + InfoTool.featureLayers[idx] || InfoTool.currentLayer, + InfoTool.currentLayerName, + null, + e, + { idx: idx }, + InfoTool.info, + InfoTool.featureLayers[idx] + ? InfoTool.featureLayers + : null + ) + } ) }) @@ -378,6 +405,34 @@ var InfoTool = { Area: F_.getFeatureArea(this.info[this.activeFeatureI], true), } + InfoTool.hasDataset = props._dataset != null + if (InfoTool.hasDataset) { + $('#infoToolSelectedDatasetDropdown').css('display', 'inherit') + const datasetNames = [] + props._dataset.results.forEach((d) => { + let name = F_.getIn(d, props._dataset.prop.split('.')) + if (name != null) + datasetNames.push( + `${props._dataset.prop.split('.').slice(-1)}: ${name}` + ) + }) + $('#infoToolSelectedDatasetDropdown').html( + Dropy.construct( + datasetNames, + `Dataset`, + InfoTool.activeDatasetI + ) + ) + Dropy.init($('#infoToolSelectedDatasetDropdown'), function (idx) { + InfoTool.activeDatasetI = idx + InfoTool.createInfo() + }) + + props.Dataset = props._dataset.results[InfoTool.activeDatasetI] + } else { + $('#infoToolSelectedDatasetDropdown').css('display', 'none') + } + depthTraversal( props, 0, @@ -396,6 +451,7 @@ var InfoTool = { type = 'infoTool_hidden' else if (path[0] == 'Coordinates') type = 'infoTool_geometry' else if (path[0] == 'Metrics') type = 'infoTool_metrics' + else if (path[0] == '_dataset') return for (var i = 0; i < keys.length; i++) { if (path.length == 0) { @@ -515,6 +571,7 @@ var InfoTool = { $('#infoToolData').empty() $('#infoToolHeader > .right').css('display', 'none') $('#infoToolSelected').css('display', 'none') + $('#infoToolSelectedDataset').css('display', 'none') $('#infoToolFilter').css('display', 'none') $('#infoToolNoneSelected').css('display', 'block') }, diff --git a/src/essence/Tools/Kinds/Kinds.js b/src/essence/Tools/Kinds/Kinds.js index f322ce347..e2de16f6d 100644 --- a/src/essence/Tools/Kinds/Kinds.js +++ b/src/essence/Tools/Kinds/Kinds.js @@ -17,6 +17,7 @@ var Kinds = { lastFeatureLayers ) { L_.setActiveFeature(layer) + if (typeof kind !== 'string') return let layerVar = {} From beabc02b5316524e104bfea95b015c26a746f24f Mon Sep 17 00:00:00 2001 From: tariqksoliman Date: Thu, 6 Mar 2025 18:48:47 -0800 Subject: [PATCH 2/2] #643 InfoTool - expanding expands property key widths too --- src/essence/Tools/Info/InfoTool.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/essence/Tools/Info/InfoTool.css b/src/essence/Tools/Info/InfoTool.css index dbf61789a..00c1fb24b 100644 --- a/src/essence/Tools/Info/InfoTool.css +++ b/src/essence/Tools/Info/InfoTool.css @@ -202,8 +202,8 @@ } #infoToolData > li > div:first-child { - max-width: 86px; - min-width: 86px; + max-width: 33%; + min-width: 33%; color: white; text-overflow: ellipsis; overflow: hidden;