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()),