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.
This commit is contained in:
Yohan Boniface 2024-02-29 13:21:14 +01:00
parent da945cf733
commit a7a854dd74
11 changed files with 73 additions and 56 deletions

View file

@ -9,6 +9,7 @@ from django.core.files.base import File
from django.core.signing import Signer from django.core.signing import Signer
from django.template.defaultfilters import slugify from django.template.defaultfilters import slugify
from django.urls import reverse from django.urls import reverse
from django.utils.functional import classproperty
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from .managers import PublicManager from .managers import PublicManager
@ -217,7 +218,7 @@ class Map(NamedModel):
"umap_id": self.pk, "umap_id": self.pk,
"onLoadPanel": "none", "onLoadPanel": "none",
"captionBar": False, "captionBar": False,
"default_iconUrl": "%sumap/img/marker.svg" % settings.STATIC_URL, "schema": self.schema,
"slideshow": {}, "slideshow": {},
} }
) )
@ -328,6 +329,17 @@ class Map(NamedModel):
datalayer.clone(map_inst=new) datalayer.clone(map_inst=new)
return 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): class Pictogram(NamedModel):
""" """

View file

@ -256,6 +256,7 @@ UMAP_DEFAULT_SHARE_STATUS = None
UMAP_DEFAULT_EDIT_STATUS = None UMAP_DEFAULT_EDIT_STATUS = None
UMAP_DEFAULT_FEATURES_HAVE_OWNERS = False UMAP_DEFAULT_FEATURES_HAVE_OWNERS = False
UMAP_HOME_FEED = "latest" UMAP_HOME_FEED = "latest"
UMAP_SCHEMA = {}
UMAP_READONLY = env("UMAP_READONLY", default=False) UMAP_READONLY = env("UMAP_READONLY", default=False)
UMAP_GZIP = True UMAP_GZIP = True

View file

@ -30,7 +30,7 @@ export default class Browser {
title.textContent = feature.getDisplayName() || '—' title.textContent = feature.getDisplayName() || '—'
const bgcolor = feature.getDynamicOption('color') const bgcolor = feature.getDynamicOption('color')
colorBox.style.backgroundColor = bgcolor 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) const icon = U.Icon.makeIconElement(symbol, colorBox)
U.Icon.setIconContrast(icon, colorBox, symbol, bgcolor) U.Icon.setIconContrast(icon, colorBox, symbol, bgcolor)
} }
@ -131,7 +131,7 @@ export default class Browser {
'h3', 'h3',
'umap-browse-title', 'umap-browse-title',
container, container,
this.map.options.name this.map.getOption('name')
) )
const formContainer = DomUtil.create('div', '', container) const formContainer = DomUtil.create('div', '', container)

View file

@ -11,18 +11,22 @@ export const SCHEMA = {
scaleControl: { scaleControl: {
type: Boolean, type: Boolean,
label: translate('Do you want to display the scale control?'), label: translate('Do you want to display the scale control?'),
default: true,
}, },
moreControl: { moreControl: {
type: Boolean, type: Boolean,
label: translate('Do you want to display the «more» control?'), label: translate('Do you want to display the «more» control?'),
default: true,
}, },
miniMap: { miniMap: {
type: Boolean, type: Boolean,
label: translate('Do you want to display a minimap?'), label: translate('Do you want to display a minimap?'),
default: false,
}, },
displayPopupFooter: { displayPopupFooter: {
type: Boolean, type: Boolean,
label: translate('Do you want to display popup footer?'), label: translate('Do you want to display popup footer?'),
default: false,
}, },
onLoadPanel: { onLoadPanel: {
type: String, type: String,
@ -74,6 +78,7 @@ export const SCHEMA = {
label: translate('color'), label: translate('color'),
helpEntries: 'colorValue', helpEntries: 'colorValue',
inheritable: true, inheritable: true,
default: 'DarkBlue',
}, },
iconClass: { iconClass: {
type: String, type: String,
@ -85,6 +90,7 @@ export const SCHEMA = {
['Drop', translate('Drop')], ['Drop', translate('Drop')],
['Ball', translate('Ball')], ['Ball', translate('Ball')],
], ],
default: 'Default',
}, },
iconUrl: { iconUrl: {
type: String, type: String,
@ -101,6 +107,7 @@ export const SCHEMA = {
label: translate('Simplify'), label: translate('Simplify'),
helpEntries: 'smoothFactor', helpEntries: 'smoothFactor',
inheritable: true, inheritable: true,
default: 1.0,
}, },
iconOpacity: { iconOpacity: {
type: Number, type: Number,
@ -109,6 +116,7 @@ export const SCHEMA = {
step: 0.1, step: 0.1,
label: translate('icon opacity'), label: translate('icon opacity'),
inheritable: true, inheritable: true,
default: 1,
}, },
opacity: { opacity: {
type: Number, type: Number,
@ -117,6 +125,7 @@ export const SCHEMA = {
step: 0.1, step: 0.1,
label: translate('opacity'), label: translate('opacity'),
inheritable: true, inheritable: true,
default: 0.5,
}, },
weight: { weight: {
type: Number, type: Number,
@ -125,12 +134,14 @@ export const SCHEMA = {
step: 1, step: 1,
label: translate('weight'), label: translate('weight'),
inheritable: true, inheritable: true,
default: 3,
}, },
fill: { fill: {
type: Boolean, type: Boolean,
label: translate('fill'), label: translate('fill'),
helpEntries: 'fill', helpEntries: 'fill',
inheritable: true, inheritable: true,
default: true,
}, },
fillColor: { fillColor: {
type: String, type: String,
@ -146,6 +157,7 @@ export const SCHEMA = {
step: 0.1, step: 0.1,
label: translate('fill opacity'), label: translate('fill opacity'),
inheritable: true, inheritable: true,
default: 0.3,
}, },
dashArray: { dashArray: {
type: String, type: String,
@ -183,6 +195,7 @@ export const SCHEMA = {
helpEntries: ['dynamicProperties', 'textFormatting'], helpEntries: ['dynamicProperties', 'textFormatting'],
placeholder: '# {name}', placeholder: '# {name}',
inheritable: true, inheritable: true,
default: '# {name}\n{description}',
}, },
zoomTo: { zoomTo: {
type: Number, type: Number,
@ -194,10 +207,12 @@ export const SCHEMA = {
captionBar: { captionBar: {
type: Boolean, type: Boolean,
label: translate('Do you want to display a caption bar?'), label: translate('Do you want to display a caption bar?'),
default: false,
}, },
captionMenus: { captionMenus: {
type: Boolean, type: Boolean,
label: translate('Do you want to display caption menus?'), label: translate('Do you want to display caption menus?'),
default: true,
}, },
slideshow: { slideshow: {
type: Object, type: Object,
@ -239,6 +254,7 @@ export const SCHEMA = {
['top', translate('On the top')], ['top', translate('On the top')],
['bottom', translate('On the bottom')], ['bottom', translate('On the bottom')],
], ],
default: 'auto',
}, },
labelInteractive: { labelInteractive: {
type: Boolean, type: Boolean,
@ -274,22 +290,26 @@ export const SCHEMA = {
permanentCreditBackground: { permanentCreditBackground: {
type: Boolean, type: Boolean,
label: translate('Permanent credits background'), label: translate('Permanent credits background'),
default: true,
}, },
zoomControl: { zoomControl: {
type: Boolean, type: Boolean,
nullable: true, nullable: true,
label: translate('Display the zoom control'), label: translate('Display the zoom control'),
default: true,
}, },
datalayersControl: { datalayersControl: {
type: Boolean, type: Boolean,
nullable: true, nullable: true,
handler: 'DataLayersControl', handler: 'DataLayersControl',
label: translate('Display the data layers control'), label: translate('Display the data layers control'),
default: true,
}, },
searchControl: { searchControl: {
type: Boolean, type: Boolean,
nullable: true, nullable: true,
label: translate('Display the search control'), label: translate('Display the search control'),
default: true,
}, },
locateControl: { locateControl: {
type: Boolean, type: Boolean,
@ -300,16 +320,19 @@ export const SCHEMA = {
type: Boolean, type: Boolean,
nullable: true, nullable: true,
label: translate('Display the fullscreen control'), label: translate('Display the fullscreen control'),
default: true,
}, },
editinosmControl: { editinosmControl: {
type: Boolean, type: Boolean,
nullable: true, nullable: true,
label: translate('Display the control to open OpenStreetMap editor'), label: translate('Display the control to open OpenStreetMap editor'),
default: null,
}, },
embedControl: { embedControl: {
type: Boolean, type: Boolean,
nullable: true, nullable: true,
label: translate('Display the embed control'), label: translate('Display the embed control'),
default: true,
}, },
measureControl: { measureControl: {
type: Boolean, type: Boolean,
@ -328,12 +351,14 @@ export const SCHEMA = {
}, },
easing: { easing: {
type: Boolean, type: Boolean,
default: false,
}, },
interactive: { interactive: {
type: Boolean, type: Boolean,
label: translate('Allow interactions'), label: translate('Allow interactions'),
helpEntries: 'interactive', helpEntries: 'interactive',
inheritable: true, inheritable: true,
default: true,
}, },
fromZoom: { fromZoom: {
type: Number, type: Number,
@ -350,6 +375,7 @@ export const SCHEMA = {
label: translate('stroke'), label: translate('stroke'),
helpEntries: 'stroke', helpEntries: 'stroke',
inheritable: true, inheritable: true,
default: true,
}, },
outlink: { outlink: {
label: translate('Link to…'), label: translate('Link to…'),

View file

@ -1184,25 +1184,22 @@ U.AttributionControl = L.Control.Attribution.extend({
this._container, this._container,
credits credits
) )
if (this._map.options.shortCredit) { const shortCredit = this._map.getOption('shortCredit'),
L.DomUtil.add( captionMenus = this._map.getOption('captionMenus')
'span', if (shortCredit) {
'', L.DomUtil.add('span', '', container, `${L.Util.toHTML(shortCredit)}`)
container,
`${L.Util.toHTML(this._map.options.shortCredit)}`
)
} }
if (this._map.options.captionMenus) { if (captionMenus) {
const link = L.DomUtil.add('a', '', container, `${L._('About')}`) const link = L.DomUtil.add('a', '', container, `${L._('About')}`)
L.DomEvent.on(link, 'click', L.DomEvent.stop) L.DomEvent.on(link, 'click', L.DomEvent.stop)
.on(link, 'click', this._map.displayCaption, this._map) .on(link, 'click', this._map.displayCaption, this._map)
.on(link, 'dblclick', L.DomEvent.stop) .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 // We are not in iframe mode
L.DomUtil.createLink('', container, `${L._('Home')}`, '/') L.DomUtil.createLink('', container, `${L._('Home')}`, '/')
} }
if (this._map.options.captionMenus) { if (captionMenus) {
L.DomUtil.createLink( L.DomUtil.createLink(
'', '',
container, container,

View file

@ -53,7 +53,7 @@ U.FeatureMixin = {
}, },
getSlug: function () { getSlug: function () {
return this.properties[this.map.options.slugKey || 'name'] || '' return this.properties[this.map.getOption('slugKey') || 'name'] || ''
}, },
getPermalink: function () { getPermalink: function () {
@ -209,7 +209,7 @@ U.FeatureMixin = {
if (L.Browser.ielt9) return false if (L.Browser.ielt9) return false
if (this.datalayer.isRemoteLayer() && this.datalayer.options.remoteData.dynamic) if (this.datalayer.isRemoteLayer() && this.datalayer.options.remoteData.dynamic)
return false return false
return this.map.options.displayPopupFooter return this.map.getOption('displayPopupFooter')
}, },
getPopupClass: function () { getPopupClass: function () {
@ -309,7 +309,7 @@ U.FeatureMixin = {
zoomTo: function (e) { zoomTo: function (e) {
e = 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) { if (easing) {
this.map.flyTo(this.getCenter(), this.getBestZoom()) this.map.flyTo(this.getCenter(), this.getBestZoom())
} else { } else {
@ -975,7 +975,7 @@ U.PathMixin = {
zoomTo: function (e) { zoomTo: function (e) {
// Use bounds instead of centroid for paths. // Use bounds instead of centroid for paths.
e = 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) { if (easing) {
this.map.flyToBounds(this.getBounds(), this.getBestZoom()) this.map.flyToBounds(this.getBounds(), this.getBestZoom())
} else { } else {

View file

@ -653,7 +653,7 @@ L.FormBuilder.IconUrl = L.FormBuilder.BlurInput.extend({
}, },
isDefault: function () { isDefault: function () {
return !this.value() || this.value() === U.DEFAULT_ICON_URL return !this.value() || this.value() === U.SCHEMA.iconUrl.default
}, },
addGrid: function (onSearch) { addGrid: function (onSearch) {

View file

@ -19,7 +19,7 @@ U.Icon = L.DivIcon.extend({
_setRecent: function (url) { _setRecent: function (url) {
if (L.Util.hasVar(url)) return 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) { if (U.Icon.RECENT.indexOf(url) === -1) {
U.Icon.RECENT.push(url) U.Icon.RECENT.push(url)
} }
@ -236,7 +236,7 @@ U.Icon.setIconContrast = function (icon, parent, src, bgcolor) {
if (L.DomUtil.contrastedColor(parent, bgcolor)) { if (L.DomUtil.contrastedColor(parent, bgcolor)) {
// Decide whether to switch svg to white or not, but do it // Decide whether to switch svg to white or not, but do it
// only for internal SVG, as invert could do weird things // 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 // Must be called after icon container is added to the DOM
// An image // An image
icon.style.filter = 'invert(1)' icon.style.filter = 'invert(1)'

View file

@ -2,33 +2,12 @@ L.Map.mergeOptions({
overlay: null, overlay: null,
datalayers: [], datalayers: [],
hash: true, 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, maxZoomLimit: 24,
attributionControl: false, attributionControl: false,
editMode: 'advanced', editMode: 'advanced',
embedControl: true,
zoomControl: true,
datalayersControl: true,
searchControl: true,
editInOSMControl: false,
editInOSMControlOptions: false,
scaleControl: true,
noControl: false, // Do not render any control. noControl: false, // Do not render any control.
miniMap: false,
name: '', name: '',
description: '', description: '',
displayPopupFooter: false,
// When a TileLayer is in TMS mode, it needs -y instead of y. // When a TileLayer is in TMS mode, it needs -y instead of y.
// This is usually handled by the TileLayer instance itself, but // This is usually handled by the TileLayer instance itself, but
// we cannot rely on this because of the y is overriden by Leaflet // we cannot rely on this because of the y is overriden by Leaflet
@ -44,14 +23,9 @@ L.Map.mergeOptions({
importPresets: [ importPresets: [
// {url: 'http://localhost:8019/en/datalayer/1502/', label: 'Simplified World Countries', format: 'geojson'} // {url: 'http://localhost:8019/en/datalayer/1502/', label: 'Simplified World Countries', format: 'geojson'}
], ],
moreControl: true,
captionBar: false,
captionMenus: true,
slideshow: {}, slideshow: {},
clickable: true, clickable: true,
easing: false,
permissions: {}, permissions: {},
permanentCreditBackground: true,
featuresHaveOwner: false, featuresHaveOwner: false,
}) })
@ -75,7 +49,8 @@ U.Map = L.Map.extend({
geojson.properties.fullscreenControl = false geojson.properties.fullscreenControl = false
L.Map.prototype.initialize.call(this, el, geojson.properties) 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 // After calling parent initialize, as we are doing initCenter our-selves
if (geojson.geometry) this.options.center = this.latLng(geojson.geometry) 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 () { initControls: function () {
this.helpMenuActions = {} this.helpMenuActions = {}
this._controls = {} this._controls = {}
@ -333,7 +314,7 @@ U.Map = L.Map.extend({
title: { false: L._('View Fullscreen'), true: L._('Exit Fullscreen') }, title: { false: L._('View Fullscreen'), true: L._('Exit Fullscreen') },
}) })
this._controls.search = new U.SearchControl() 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) this._controls.tilelayersChooser = new U.TileLayerChooser(this)
if (this.options.user) this._controls.star = new U.StarControl(this) if (this.options.user) this._controls.star = new U.StarControl(this)
this._controls.editinosm = new L.Control.EditInOSM({ this._controls.editinosm = new L.Control.EditInOSM({
@ -399,7 +380,7 @@ U.Map = L.Map.extend({
let name, status, control let name, status, control
for (let i = 0; i < this.HIDDABLE_CONTROLS.length; i++) { for (let i = 0; i < this.HIDDABLE_CONTROLS.length; i++) {
name = this.HIDDABLE_CONTROLS[i] name = this.HIDDABLE_CONTROLS[i]
status = this.options[`${name}Control`] status = this.getOption(`${name}Control`)
if (status === false) continue if (status === false) continue
control = this._controls[name] control = this._controls[name]
if (!control) continue if (!control) continue
@ -408,9 +389,9 @@ U.Map = L.Map.extend({
L.DomUtil.addClass(control._container, 'display-on-more') L.DomUtil.addClass(control._container, 'display-on-more')
else L.DomUtil.removeClass(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.getOption('permanentCredit')) this._controls.permanentCredit.addTo(this)
if (this.options.moreControl) this._controls.more.addTo(this) if (this.getOption('moreControl')) this._controls.more.addTo(this)
if (this.options.scaleControl) this._controls.scale.addTo(this) if (this.getOption('scaleControl')) this._controls.scale.addTo(this)
}, },
initDataLayers: async function (datalayers) { initDataLayers: async function (datalayers) {
@ -760,7 +741,7 @@ U.Map = L.Map.extend({
}, },
getDefaultOption: function (option) { getDefaultOption: function (option) {
return this.options[`default_${option}`] return U.SCHEMA[option] && U.SCHEMA[option].default
}, },
getOption: function (option) { getOption: function (option) {
@ -1607,7 +1588,7 @@ U.Map = L.Map.extend({
name = L.DomUtil.create('h3', '', container) name = L.DomUtil.create('h3', '', container)
L.DomEvent.disableClickPropagation(container) L.DomEvent.disableClickPropagation(container)
this.permissions.addOwnerLink('span', container) this.permissions.addOwnerLink('span', container)
if (this.options.captionMenus) { if (this.getOption('captionMenus')) {
L.DomUtil.createButton( L.DomUtil.createButton(
'umap-about-link flat', 'umap-about-link flat',
container, container,

View file

@ -215,7 +215,7 @@ U.IframeExporter = L.Evented.extend({
this.map = map this.map = map
this.baseUrl = L.Util.getBaseUrl() this.baseUrl = L.Util.getBaseUrl()
// Use map default, not generic default // Use map default, not generic default
this.queryString.onLoadPanel = this.map.options.onLoadPanel this.queryString.onLoadPanel = this.map.getOption('onLoadPanel')
}, },
getMap: function () { getMap: function () {

View file

@ -488,7 +488,7 @@ class MapDetailMixin:
"urls": _urls_for_js(), "urls": _urls_for_js(),
"tilelayers": TileLayer.get_list(), "tilelayers": TileLayer.get_list(),
"editMode": self.edit_mode, "editMode": self.edit_mode,
"default_iconUrl": "%sumap/img/marker.svg" % settings.STATIC_URL, # noqa "schema": Map.schema,
"umap_id": self.get_umap_id(), "umap_id": self.get_umap_id(),
"starred": self.is_starred(), "starred": self.is_starred(),
"licences": dict((l.name, l.json) for l in Licence.objects.all()), "licences": dict((l.name, l.json) for l in Licence.objects.all()),