From d683c1b4599a93724c108d08614f25e4c75309a3 Mon Sep 17 00:00:00 2001 From: Yohan Boniface Date: Thu, 21 Nov 2024 14:20:05 +0100 Subject: [PATCH 1/2] feat: allow to configure the default label keys per instance fix #2289 --- docs/config/settings.md | 15 ++++++--- umap/settings/base.py | 1 + umap/static/umap/js/modules/data/features.js | 33 +++++++++++-------- umap/static/umap/js/modules/data/layer.js | 9 +++-- .../umap/js/modules/rendering/template.js | 4 ++- umap/static/umap/js/modules/rendering/ui.js | 2 +- umap/static/umap/js/modules/tableeditor.js | 2 +- umap/static/umap/js/modules/umap.js | 4 +++ umap/static/umap/js/modules/utils.js | 2 +- umap/tests/integration/test_browser.py | 17 +++++++++- umap/views.py | 1 + 11 files changed, 64 insertions(+), 26 deletions(-) diff --git a/docs/config/settings.md b/docs/config/settings.md index 481855d36..88ed527d1 100644 --- a/docs/config/settings.md +++ b/docs/config/settings.md @@ -175,11 +175,6 @@ UMAP_EXTRA_URLS = { } ``` -#### UMAP_KEEP_VERSIONS - -How many datalayer versions to keep. 10 by default. - - #### UMAP_DEFAULT_EDIT_STATUS Define the map default edit status. @@ -270,6 +265,16 @@ Available importers: - `communesfr`: download French communes boundaries, from https://geo.api.gouv.fr/ - `datasets`: define URLs you want to promote to users, with a `name` and a `format` +#### UMAP_KEEP_VERSIONS + +How many datalayer versions to keep. 10 by default. + +#### UMAP_LABEL_KEYS + +List of properties to consider as "Feature label" (to show in popup or in browser). + + UMAP_LABEL_KEYS = ["name", "title"] + #### UMAP_MAPS_PER_PAGE How many maps to show in maps list, like search or home page. diff --git a/umap/settings/base.py b/umap/settings/base.py index 3de78ce61..8c61146c4 100644 --- a/umap/settings/base.py +++ b/umap/settings/base.py @@ -273,6 +273,7 @@ UMAP_IMPORTERS = {} UMAP_HOST_INFOS = {} UMAP_PURGATORY_ROOT = "/tmp/umappurgatory" +UMAP_LABEL_KEYS = ["name", "title"] UMAP_READONLY = env("UMAP_READONLY", default=False) UMAP_GZIP = True diff --git a/umap/static/umap/js/modules/data/features.js b/umap/static/umap/js/modules/data/features.js index 76702d620..70c4b50e0 100644 --- a/umap/static/umap/js/modules/data/features.js +++ b/umap/static/umap/js/modules/data/features.js @@ -165,7 +165,9 @@ class Feature { } getSlug() { - return this.properties[this._umap.getProperty('slugKey') || 'name'] || '' + return ( + this.properties[this._umap.getProperty('slugKey') || U.DEFAULT_LABEL_KEY] || '' + ) } getPermalink() { @@ -235,14 +237,14 @@ class Feature { const properties = [] for (const property of this.datalayer._propertiesIndex) { - if (['name', 'description'].includes(property)) { + if ([U.DEFAULT_LABEL_KEY, 'description'].includes(property)) { continue } properties.push([`properties.${property}`, { label: property }]) } // We always want name and description for now (properties management to come) properties.unshift('properties.description') - properties.unshift('properties.name') + properties.unshift(`properties.${U.DEFAULT_LABEL_KEY}`) builder = new U.FormBuilder(this, properties, { id: 'umap-feature-properties', }) @@ -316,19 +318,22 @@ class Feature { endEdit() {} - getDisplayName(fallback) { - const key = this.getOption('labelKey') || 'name' + getDisplayName() { + const keys = U.LABEL_KEYS.slice() // Copy. + const labelKey = this.getOption('labelKey') // Variables mode. - if (Utils.hasVar(key)) { - return Utils.greedyTemplate(key, this.extendedProperties()) + if (labelKey) { + if (Utils.hasVar(labelKey)) { + return Utils.greedyTemplate(labelKey, this.extendedProperties()) + } + keys.unshift(labelKey) } - // Simple mode. - return ( - this.properties[key] || - this.properties.title || - fallback || - this.datalayer.getName() - ) + for (const key of keys) { + const value = this.properties[key] + console.log(key, value) + if (value) return value + } + return this.datalayer.getName() } hasPopupFooter() { diff --git a/umap/static/umap/js/modules/data/layer.js b/umap/static/umap/js/modules/data/layer.js index cdb2c9ee4..31a84b778 100644 --- a/umap/static/umap/js/modules/data/layer.js +++ b/umap/static/umap/js/modules/data/layer.js @@ -289,7 +289,7 @@ export class DataLayer extends ServerStored { reindex() { const features = Object.values(this._features) - Utils.sortFeatures(features, this._umap.getProperty('sortKey'), U.lang) + this.sortFeatures(features) this._index = features.map((feature) => stamp(feature)) } @@ -446,6 +446,11 @@ export class DataLayer extends ServerStored { } } + sortFeatures(collection) { + const sortKeys = this._umap.getProperty('sortKey') || U.DEFAULT_LABEL_KEY + return Utils.sortFeatures(collection, sortKeys, U.lang) + } + makeFeatures(geojson = {}, sync = true) { if (geojson.type === 'Feature' || geojson.coordinates) { geojson = [geojson] @@ -454,7 +459,7 @@ export class DataLayer extends ServerStored { ? geojson : geojson.features || geojson.geometries if (!collection) return - Utils.sortFeatures(collection, this._umap.getProperty('sortKey'), U.lang) + this.sortFeatures(collection) for (const feature of collection) { this.makeFeature(feature, sync) } diff --git a/umap/static/umap/js/modules/rendering/template.js b/umap/static/umap/js/modules/rendering/template.js index 7f62acfa2..9ff89cbec 100644 --- a/umap/static/umap/js/modules/rendering/template.js +++ b/umap/static/umap/js/modules/rendering/template.js @@ -115,7 +115,9 @@ class Table extends TitleMixin(PopupTemplate) { const table = document.createElement('table') for (const key in feature.properties) { - if (typeof feature.properties[key] === 'object' || key === 'name') continue + if (typeof feature.properties[key] === 'object' || U.LABEL_KEYS.includes(key)) { + continue + } table.appendChild(this.makeRow(feature, key)) } return table diff --git a/umap/static/umap/js/modules/rendering/ui.js b/umap/static/umap/js/modules/rendering/ui.js index f7779389a..18fabe510 100644 --- a/umap/static/umap/js/modules/rendering/ui.js +++ b/umap/static/umap/js/modules/rendering/ui.js @@ -75,7 +75,7 @@ const FeatureMixin = { resetTooltip: function () { if (!this.feature.hasGeom()) return - const displayName = this.feature.getDisplayName(null) + const displayName = this.feature.getDisplayName() let showLabel = this.feature.getOption('showLabel') const oldLabelHover = this.feature.getOption('labelHover') diff --git a/umap/static/umap/js/modules/tableeditor.js b/umap/static/umap/js/modules/tableeditor.js index 0a2b49ed3..0cabf37ae 100644 --- a/umap/static/umap/js/modules/tableeditor.js +++ b/umap/static/umap/js/modules/tableeditor.js @@ -105,7 +105,7 @@ export default class TableEditor extends WithTemplate { resetProperties() { this.properties = this.datalayer._propertiesIndex if (this.properties.length === 0) { - this.properties = ['name', 'description'] + this.properties = [U.DEFAULT_LABEL_KEY, 'description'] } } diff --git a/umap/static/umap/js/modules/umap.js b/umap/static/umap/js/modules/umap.js index b06968276..dc6df49d2 100644 --- a/umap/static/umap/js/modules/umap.js +++ b/umap/static/umap/js/modules/umap.js @@ -71,6 +71,10 @@ export default class Umap extends ServerStored { // To be used in javascript APIs if (geojson.properties.lang) U.lang = geojson.properties.lang + // Make it available to utils, without needing a reference to `Umap`. + U.LABEL_KEYS = geojson.properties.defaultLabelKeys || [] + U.DEFAULT_LABEL_KEY = U.LABEL_KEYS[0] || 'name' + this.setPropertiesFromQueryString() // Needed for actions labels diff --git a/umap/static/umap/js/modules/utils.js b/umap/static/umap/js/modules/utils.js index 9e4feeed5..19085ec3a 100644 --- a/umap/static/umap/js/modules/utils.js +++ b/umap/static/umap/js/modules/utils.js @@ -292,7 +292,7 @@ export function naturalSort(a, b, lang) { } export function sortFeatures(features, sortKey, lang) { - const sortKeys = (sortKey || 'name').split(',') + const sortKeys = sortKey.split(',') const sort = (a, b, i) => { let sortKey = sortKeys[i] diff --git a/umap/tests/integration/test_browser.py b/umap/tests/integration/test_browser.py index 7391f173c..d6bd507cb 100644 --- a/umap/tests/integration/test_browser.py +++ b/umap/tests/integration/test_browser.py @@ -15,7 +15,12 @@ "features": [ { "type": "Feature", - "properties": {"name": "one point in france", "foo": "point", "bar": "one"}, + "properties": { + "name": "one point in france", + "foo": "point", + "bar": "one", + "label": "this is label one", + }, "geometry": {"type": "Point", "coordinates": [3.339844, 46.920255]}, }, { @@ -24,6 +29,7 @@ "name": "one polygon in greenland", "foo": "polygon", "bar": "two", + "label": "this is label two", }, "geometry": { "type": "Polygon", @@ -44,6 +50,7 @@ "name": "one line in new zeland", "foo": "line", "bar": "three", + "label": "this is label three", }, "geometry": { "type": "LineString", @@ -476,3 +483,11 @@ def test_main_toolbox_toggle_all_layers(live_server, map, page): # Should hidden again all layers expect(page.locator(".datalayer.off")).to_have_count(3) expect(markers).to_have_count(0) + + +def test_honour_the_label_fields_settings(live_server, map, page, bootstrap, settings): + settings.UMAP_LABEL_KEYS = ["label", "name"] + page.goto(f"{live_server.url}{map.get_absolute_url()}") + expect(page.locator(".panel").get_by_text("this is label one")).to_be_visible() + expect(page.locator(".panel").get_by_text("this is label two")).to_be_visible() + expect(page.locator(".panel").get_by_text("this is label three")).to_be_visible() diff --git a/umap/views.py b/umap/views.py index 864cc57b0..e56d02006 100644 --- a/umap/views.py +++ b/umap/views.py @@ -609,6 +609,7 @@ def get_map_properties(self): "websocketEnabled": settings.WEBSOCKET_ENABLED, "websocketURI": settings.WEBSOCKET_FRONT_URI, "importers": settings.UMAP_IMPORTERS, + "defaultLabelKeys": settings.UMAP_LABEL_KEYS, } created = bool(getattr(self, "object", None)) if (created and self.object.owner) or (not created and not user.is_anonymous): From 3e58a9d161bfb7032770a8af62749dd98b28dc17 Mon Sep 17 00:00:00 2001 From: Yohan Boniface Date: Fri, 22 Nov 2024 10:37:00 +0100 Subject: [PATCH 2/2] fix: don't show default label if another is found in features edit --- umap/static/umap/js/modules/data/features.js | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/umap/static/umap/js/modules/data/features.js b/umap/static/umap/js/modules/data/features.js index 70c4b50e0..d148779cc 100644 --- a/umap/static/umap/js/modules/data/features.js +++ b/umap/static/umap/js/modules/data/features.js @@ -236,15 +236,23 @@ class Feature { container.appendChild(builder.build()) const properties = [] + let labelKeyFound = undefined for (const property of this.datalayer._propertiesIndex) { - if ([U.DEFAULT_LABEL_KEY, 'description'].includes(property)) { + if (!labelKeyFound && U.LABEL_KEYS.includes(property)) { + labelKeyFound = property + continue + } + if (property === 'description') { continue } properties.push([`properties.${property}`, { label: property }]) } // We always want name and description for now (properties management to come) properties.unshift('properties.description') - properties.unshift(`properties.${U.DEFAULT_LABEL_KEY}`) + if (!labelKeyFound) { + labelKeyFound = U.DEFAULT_LABEL_KEY + } + properties.unshift([`properties.${labelKeyFound}`, { label: labelKeyFound }]) builder = new U.FormBuilder(this, properties, { id: 'umap-feature-properties', }) @@ -257,7 +265,7 @@ class Feature { this.getAdvancedEditActions(advancedActions) const onLoad = this._umap.editPanel.open({ content: container }) onLoad.then(() => { - builder.helpers['properties.name'].input.focus() + builder.helpers[`properties.${labelKeyFound}`].input.focus() }) this._umap.editedFeature = this if (!this.ui.isOnScreen(this._umap._leafletMap.getBounds())) this.zoomTo(event) @@ -330,7 +338,6 @@ class Feature { } for (const key of keys) { const value = this.properties[key] - console.log(key, value) if (value) return value } return this.datalayer.getName()