diff --git a/docs/config/settings.md b/docs/config/settings.md index 481855d3..88ed527d 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 3de78ce6..8c61146c 100644 --- a/umap/settings/base.py +++ b/umap/settings/base.py @@ -273,6 +273,7 @@ UMAP_HOME_FEED = "latest" 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 76702d62..d148779c 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() { @@ -234,15 +236,23 @@ class Feature { container.appendChild(builder.build()) const properties = [] + let labelKeyFound = undefined for (const property of this.datalayer._propertiesIndex) { - if (['name', '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.name') + if (!labelKeyFound) { + labelKeyFound = U.DEFAULT_LABEL_KEY + } + properties.unshift([`properties.${labelKeyFound}`, { label: labelKeyFound }]) builder = new U.FormBuilder(this, properties, { id: 'umap-feature-properties', }) @@ -255,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) @@ -316,19 +326,21 @@ 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] + 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 6a3e3c74..a362ee71 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)) } @@ -450,6 +450,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] @@ -458,7 +463,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 e0738a39..33fbfb9d 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 8e0e28e5..a67c9d87 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 0a2b49ed..0cabf37a 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 1784e8bf..68a325d2 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 9e4feeed..19085ec3 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 7391f173..d6bd507c 100644 --- a/umap/tests/integration/test_browser.py +++ b/umap/tests/integration/test_browser.py @@ -15,7 +15,12 @@ DATALAYER_DATA = { "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 @@ DATALAYER_DATA = { "name": "one polygon in greenland", "foo": "polygon", "bar": "two", + "label": "this is label two", }, "geometry": { "type": "Polygon", @@ -44,6 +50,7 @@ DATALAYER_DATA = { "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 864cc57b..e56d0200 100644 --- a/umap/views.py +++ b/umap/views.py @@ -609,6 +609,7 @@ class MapDetailMixin(SessionMixin): "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):