From a7a854dd74beeff5153cbc44aaad64d7f2aab1c2 Mon Sep 17 00:00:00 2001 From: Yohan Boniface Date: Thu, 29 Feb 2024 13:21:14 +0100 Subject: [PATCH] wip: move default options to schema This commit also introduce a new settings UMAP_SCHEMA, that could be used to easily override schema default, like the default color, the default path weigth and so on. I'm not documenting yet, because I'm not yet totally sure we want this. --- umap/models.py | 14 +++++++- umap/settings/base.py | 1 + umap/static/umap/js/modules/browser.js | 4 +-- umap/static/umap/js/modules/schema.js | 26 ++++++++++++++ umap/static/umap/js/umap.controls.js | 17 ++++----- umap/static/umap/js/umap.features.js | 8 ++--- umap/static/umap/js/umap.forms.js | 2 +- umap/static/umap/js/umap.icon.js | 4 +-- umap/static/umap/js/umap.js | 49 ++++++++------------------ umap/static/umap/js/umap.share.js | 2 +- umap/views.py | 2 +- 11 files changed, 73 insertions(+), 56 deletions(-) diff --git a/umap/models.py b/umap/models.py index 4bff5f75..857a01f5 100644 --- a/umap/models.py +++ b/umap/models.py @@ -9,6 +9,7 @@ from django.core.files.base import File from django.core.signing import Signer from django.template.defaultfilters import slugify from django.urls import reverse +from django.utils.functional import classproperty from django.utils.translation import gettext_lazy as _ from .managers import PublicManager @@ -217,7 +218,7 @@ class Map(NamedModel): "umap_id": self.pk, "onLoadPanel": "none", "captionBar": False, - "default_iconUrl": "%sumap/img/marker.svg" % settings.STATIC_URL, + "schema": self.schema, "slideshow": {}, } ) @@ -328,6 +329,17 @@ class Map(NamedModel): datalayer.clone(map_inst=new) return new + @classproperty + def schema(self): + schema = settings.UMAP_SCHEMA + schema.setdefault( + "iconUrl", + { + "default": "%sumap/img/marker.svg" % settings.STATIC_URL, + }, + ) + return schema + class Pictogram(NamedModel): """ diff --git a/umap/settings/base.py b/umap/settings/base.py index d29d08eb..6766ea42 100644 --- a/umap/settings/base.py +++ b/umap/settings/base.py @@ -256,6 +256,7 @@ UMAP_DEFAULT_SHARE_STATUS = None UMAP_DEFAULT_EDIT_STATUS = None UMAP_DEFAULT_FEATURES_HAVE_OWNERS = False UMAP_HOME_FEED = "latest" +UMAP_SCHEMA = {} UMAP_READONLY = env("UMAP_READONLY", default=False) UMAP_GZIP = True diff --git a/umap/static/umap/js/modules/browser.js b/umap/static/umap/js/modules/browser.js index c3370acf..80c7f358 100644 --- a/umap/static/umap/js/modules/browser.js +++ b/umap/static/umap/js/modules/browser.js @@ -30,7 +30,7 @@ export default class Browser { title.textContent = feature.getDisplayName() || '—' const bgcolor = feature.getDynamicOption('color') colorBox.style.backgroundColor = bgcolor - if (symbol && symbol !== this.map.options.default_iconUrl) { + if (symbol && symbol !== U.SCHEMA.iconUrl.default) { const icon = U.Icon.makeIconElement(symbol, colorBox) U.Icon.setIconContrast(icon, colorBox, symbol, bgcolor) } @@ -131,7 +131,7 @@ export default class Browser { 'h3', 'umap-browse-title', container, - this.map.options.name + this.map.getOption('name') ) const formContainer = DomUtil.create('div', '', container) diff --git a/umap/static/umap/js/modules/schema.js b/umap/static/umap/js/modules/schema.js index 39b26864..13c25b70 100644 --- a/umap/static/umap/js/modules/schema.js +++ b/umap/static/umap/js/modules/schema.js @@ -11,18 +11,22 @@ export const SCHEMA = { scaleControl: { type: Boolean, label: translate('Do you want to display the scale control?'), + default: true, }, moreControl: { type: Boolean, label: translate('Do you want to display the «more» control?'), + default: true, }, miniMap: { type: Boolean, label: translate('Do you want to display a minimap?'), + default: false, }, displayPopupFooter: { type: Boolean, label: translate('Do you want to display popup footer?'), + default: false, }, onLoadPanel: { type: String, @@ -74,6 +78,7 @@ export const SCHEMA = { label: translate('color'), helpEntries: 'colorValue', inheritable: true, + default: 'DarkBlue', }, iconClass: { type: String, @@ -85,6 +90,7 @@ export const SCHEMA = { ['Drop', translate('Drop')], ['Ball', translate('Ball')], ], + default: 'Default', }, iconUrl: { type: String, @@ -101,6 +107,7 @@ export const SCHEMA = { label: translate('Simplify'), helpEntries: 'smoothFactor', inheritable: true, + default: 1.0, }, iconOpacity: { type: Number, @@ -109,6 +116,7 @@ export const SCHEMA = { step: 0.1, label: translate('icon opacity'), inheritable: true, + default: 1, }, opacity: { type: Number, @@ -117,6 +125,7 @@ export const SCHEMA = { step: 0.1, label: translate('opacity'), inheritable: true, + default: 0.5, }, weight: { type: Number, @@ -125,12 +134,14 @@ export const SCHEMA = { step: 1, label: translate('weight'), inheritable: true, + default: 3, }, fill: { type: Boolean, label: translate('fill'), helpEntries: 'fill', inheritable: true, + default: true, }, fillColor: { type: String, @@ -146,6 +157,7 @@ export const SCHEMA = { step: 0.1, label: translate('fill opacity'), inheritable: true, + default: 0.3, }, dashArray: { type: String, @@ -183,6 +195,7 @@ export const SCHEMA = { helpEntries: ['dynamicProperties', 'textFormatting'], placeholder: '# {name}', inheritable: true, + default: '# {name}\n{description}', }, zoomTo: { type: Number, @@ -194,10 +207,12 @@ export const SCHEMA = { captionBar: { type: Boolean, label: translate('Do you want to display a caption bar?'), + default: false, }, captionMenus: { type: Boolean, label: translate('Do you want to display caption menus?'), + default: true, }, slideshow: { type: Object, @@ -239,6 +254,7 @@ export const SCHEMA = { ['top', translate('On the top')], ['bottom', translate('On the bottom')], ], + default: 'auto', }, labelInteractive: { type: Boolean, @@ -274,22 +290,26 @@ export const SCHEMA = { permanentCreditBackground: { type: Boolean, label: translate('Permanent credits background'), + default: true, }, zoomControl: { type: Boolean, nullable: true, label: translate('Display the zoom control'), + default: true, }, datalayersControl: { type: Boolean, nullable: true, handler: 'DataLayersControl', label: translate('Display the data layers control'), + default: true, }, searchControl: { type: Boolean, nullable: true, label: translate('Display the search control'), + default: true, }, locateControl: { type: Boolean, @@ -300,16 +320,19 @@ export const SCHEMA = { type: Boolean, nullable: true, label: translate('Display the fullscreen control'), + default: true, }, editinosmControl: { type: Boolean, nullable: true, label: translate('Display the control to open OpenStreetMap editor'), + default: null, }, embedControl: { type: Boolean, nullable: true, label: translate('Display the embed control'), + default: true, }, measureControl: { type: Boolean, @@ -328,12 +351,14 @@ export const SCHEMA = { }, easing: { type: Boolean, + default: false, }, interactive: { type: Boolean, label: translate('Allow interactions'), helpEntries: 'interactive', inheritable: true, + default: true, }, fromZoom: { type: Number, @@ -350,6 +375,7 @@ export const SCHEMA = { label: translate('stroke'), helpEntries: 'stroke', inheritable: true, + default: true, }, outlink: { label: translate('Link to…'), diff --git a/umap/static/umap/js/umap.controls.js b/umap/static/umap/js/umap.controls.js index b76daccc..cfbd78bf 100644 --- a/umap/static/umap/js/umap.controls.js +++ b/umap/static/umap/js/umap.controls.js @@ -1184,25 +1184,22 @@ U.AttributionControl = L.Control.Attribution.extend({ this._container, credits ) - if (this._map.options.shortCredit) { - L.DomUtil.add( - 'span', - '', - container, - ` — ${L.Util.toHTML(this._map.options.shortCredit)}` - ) + const shortCredit = this._map.getOption('shortCredit'), + captionMenus = this._map.getOption('captionMenus') + if (shortCredit) { + L.DomUtil.add('span', '', container, ` — ${L.Util.toHTML(shortCredit)}`) } - if (this._map.options.captionMenus) { + if (captionMenus) { const link = L.DomUtil.add('a', '', container, ` — ${L._('About')}`) L.DomEvent.on(link, 'click', L.DomEvent.stop) .on(link, 'click', this._map.displayCaption, this._map) .on(link, 'dblclick', L.DomEvent.stop) } - if (window.top === window.self && this._map.options.captionMenus) { + if (window.top === window.self && captionMenus) { // We are not in iframe mode L.DomUtil.createLink('', container, ` — ${L._('Home')}`, '/') } - if (this._map.options.captionMenus) { + if (captionMenus) { L.DomUtil.createLink( '', container, diff --git a/umap/static/umap/js/umap.features.js b/umap/static/umap/js/umap.features.js index 57caecce..16ea166d 100644 --- a/umap/static/umap/js/umap.features.js +++ b/umap/static/umap/js/umap.features.js @@ -53,7 +53,7 @@ U.FeatureMixin = { }, getSlug: function () { - return this.properties[this.map.options.slugKey || 'name'] || '' + return this.properties[this.map.getOption('slugKey') || 'name'] || '' }, getPermalink: function () { @@ -209,7 +209,7 @@ U.FeatureMixin = { if (L.Browser.ielt9) return false if (this.datalayer.isRemoteLayer() && this.datalayer.options.remoteData.dynamic) return false - return this.map.options.displayPopupFooter + return this.map.getOption('displayPopupFooter') }, getPopupClass: function () { @@ -309,7 +309,7 @@ U.FeatureMixin = { zoomTo: function (e) { e = e || {} - const easing = e.easing !== undefined ? e.easing : this.map.options.easing + const easing = e.easing !== undefined ? e.easing : this.map.getOption('easing') if (easing) { this.map.flyTo(this.getCenter(), this.getBestZoom()) } else { @@ -975,7 +975,7 @@ U.PathMixin = { zoomTo: function (e) { // Use bounds instead of centroid for paths. e = e || {} - const easing = e.easing !== undefined ? e.easing : this.map.options.easing + const easing = e.easing !== undefined ? e.easing : this.map.getOption('easing') if (easing) { this.map.flyToBounds(this.getBounds(), this.getBestZoom()) } else { diff --git a/umap/static/umap/js/umap.forms.js b/umap/static/umap/js/umap.forms.js index 958329d3..919711eb 100644 --- a/umap/static/umap/js/umap.forms.js +++ b/umap/static/umap/js/umap.forms.js @@ -653,7 +653,7 @@ L.FormBuilder.IconUrl = L.FormBuilder.BlurInput.extend({ }, isDefault: function () { - return !this.value() || this.value() === U.DEFAULT_ICON_URL + return !this.value() || this.value() === U.SCHEMA.iconUrl.default }, addGrid: function (onSearch) { diff --git a/umap/static/umap/js/umap.icon.js b/umap/static/umap/js/umap.icon.js index 5a0189b3..7eae6575 100644 --- a/umap/static/umap/js/umap.icon.js +++ b/umap/static/umap/js/umap.icon.js @@ -19,7 +19,7 @@ U.Icon = L.DivIcon.extend({ _setRecent: function (url) { if (L.Util.hasVar(url)) return - if (url === U.DEFAULT_ICON_URL) return + if (url === U.SCHEMA.iconUrl.default) return if (U.Icon.RECENT.indexOf(url) === -1) { U.Icon.RECENT.push(url) } @@ -236,7 +236,7 @@ U.Icon.setIconContrast = function (icon, parent, src, bgcolor) { if (L.DomUtil.contrastedColor(parent, bgcolor)) { // Decide whether to switch svg to white or not, but do it // only for internal SVG, as invert could do weird things - if (L.Util.isPath(src) && src.endsWith('.svg') && src !== U.DEFAULT_ICON_URL) { + if (L.Util.isPath(src) && src.endsWith('.svg') && src !== U.SCHEMA.iconUrl.default) { // Must be called after icon container is added to the DOM // An image icon.style.filter = 'invert(1)' diff --git a/umap/static/umap/js/umap.js b/umap/static/umap/js/umap.js index 2fe72afb..b4df88cf 100644 --- a/umap/static/umap/js/umap.js +++ b/umap/static/umap/js/umap.js @@ -2,33 +2,12 @@ L.Map.mergeOptions({ overlay: null, datalayers: [], hash: true, - default_color: 'DarkBlue', - default_smoothFactor: 1.0, - default_opacity: 0.5, - default_fillOpacity: 0.3, - default_stroke: true, - default_fill: true, - default_weight: 3, - default_iconOpacity: 1, - default_iconClass: 'Default', - default_popupContentTemplate: '# {name}\n{description}', - default_interactive: true, - default_labelDirection: 'auto', maxZoomLimit: 24, attributionControl: false, editMode: 'advanced', - embedControl: true, - zoomControl: true, - datalayersControl: true, - searchControl: true, - editInOSMControl: false, - editInOSMControlOptions: false, - scaleControl: true, noControl: false, // Do not render any control. - miniMap: false, name: '', description: '', - displayPopupFooter: false, // When a TileLayer is in TMS mode, it needs -y instead of y. // This is usually handled by the TileLayer instance itself, but // we cannot rely on this because of the y is overriden by Leaflet @@ -44,14 +23,9 @@ L.Map.mergeOptions({ importPresets: [ // {url: 'http://localhost:8019/en/datalayer/1502/', label: 'Simplified World Countries', format: 'geojson'} ], - moreControl: true, - captionBar: false, - captionMenus: true, slideshow: {}, clickable: true, - easing: false, permissions: {}, - permanentCreditBackground: true, featuresHaveOwner: false, }) @@ -75,7 +49,8 @@ U.Map = L.Map.extend({ geojson.properties.fullscreenControl = false L.Map.prototype.initialize.call(this, el, geojson.properties) - U.DEFAULT_ICON_URL = this.options.default_iconUrl + + if (geojson.properties.schema) this.overrideSchema(geojson.properties.schema) // After calling parent initialize, as we are doing initCenter our-selves if (geojson.geometry) this.options.center = this.latLng(geojson.geometry) @@ -292,6 +267,12 @@ U.Map = L.Map.extend({ } }, + overrideSchema: function (schema) { + for (const [key, extra] of Object.entries(schema)) { + U.SCHEMA[key] = L.extend({}, U.SCHEMA[key], extra) + } + }, + initControls: function () { this.helpMenuActions = {} this._controls = {} @@ -333,7 +314,7 @@ U.Map = L.Map.extend({ title: { false: L._('View Fullscreen'), true: L._('Exit Fullscreen') }, }) this._controls.search = new U.SearchControl() - this._controls.embed = new L.Control.Embed(this, this.options.embedOptions) + this._controls.embed = new L.Control.Embed(this) this._controls.tilelayersChooser = new U.TileLayerChooser(this) if (this.options.user) this._controls.star = new U.StarControl(this) this._controls.editinosm = new L.Control.EditInOSM({ @@ -399,7 +380,7 @@ U.Map = L.Map.extend({ let name, status, control for (let i = 0; i < this.HIDDABLE_CONTROLS.length; i++) { name = this.HIDDABLE_CONTROLS[i] - status = this.options[`${name}Control`] + status = this.getOption(`${name}Control`) if (status === false) continue control = this._controls[name] if (!control) continue @@ -408,9 +389,9 @@ U.Map = L.Map.extend({ L.DomUtil.addClass(control._container, 'display-on-more') else L.DomUtil.removeClass(control._container, 'display-on-more') } - if (this.options.permanentCredit) this._controls.permanentCredit.addTo(this) - if (this.options.moreControl) this._controls.more.addTo(this) - if (this.options.scaleControl) this._controls.scale.addTo(this) + if (this.getOption('permanentCredit')) this._controls.permanentCredit.addTo(this) + if (this.getOption('moreControl')) this._controls.more.addTo(this) + if (this.getOption('scaleControl')) this._controls.scale.addTo(this) }, initDataLayers: async function (datalayers) { @@ -760,7 +741,7 @@ U.Map = L.Map.extend({ }, getDefaultOption: function (option) { - return this.options[`default_${option}`] + return U.SCHEMA[option] && U.SCHEMA[option].default }, getOption: function (option) { @@ -1607,7 +1588,7 @@ U.Map = L.Map.extend({ name = L.DomUtil.create('h3', '', container) L.DomEvent.disableClickPropagation(container) this.permissions.addOwnerLink('span', container) - if (this.options.captionMenus) { + if (this.getOption('captionMenus')) { L.DomUtil.createButton( 'umap-about-link flat', container, diff --git a/umap/static/umap/js/umap.share.js b/umap/static/umap/js/umap.share.js index 167f73d1..ad7c7b14 100644 --- a/umap/static/umap/js/umap.share.js +++ b/umap/static/umap/js/umap.share.js @@ -215,7 +215,7 @@ U.IframeExporter = L.Evented.extend({ this.map = map this.baseUrl = L.Util.getBaseUrl() // Use map default, not generic default - this.queryString.onLoadPanel = this.map.options.onLoadPanel + this.queryString.onLoadPanel = this.map.getOption('onLoadPanel') }, getMap: function () { diff --git a/umap/views.py b/umap/views.py index 8bbc936b..fcdcd1b5 100644 --- a/umap/views.py +++ b/umap/views.py @@ -488,7 +488,7 @@ class MapDetailMixin: "urls": _urls_for_js(), "tilelayers": TileLayer.get_list(), "editMode": self.edit_mode, - "default_iconUrl": "%sumap/img/marker.svg" % settings.STATIC_URL, # noqa + "schema": Map.schema, "umap_id": self.get_umap_id(), "starred": self.is_starred(), "licences": dict((l.name, l.json) for l in Licence.objects.all()),