refactor: Separate ui rendering from data updates
`U.SCHEMA` now contains an `impacts` key, which makes it possible to specify what part of the UI is impacted by data changes. A new `render` method has been added on `U.Map` and `U.DataLayer`, which is used to rerender the proper parts of the UI depending on the passed properties. `U.FormBuilder` calls this `render()` method (if present), during form changes.
This commit is contained in:
parent
99a0007ce0
commit
55cc7a098f
5 changed files with 416 additions and 330 deletions
|
@ -1,45 +1,52 @@
|
||||||
import { translate } from './i18n.js'
|
import { translate } from './i18n.js'
|
||||||
|
|
||||||
|
// Possible impacts
|
||||||
|
// ['ui', 'data', 'limit-bounds', 'datalayer-index', 'remote-data', 'background']
|
||||||
|
|
||||||
export const SCHEMA = {
|
export const SCHEMA = {
|
||||||
zoom: {
|
browsable: {
|
||||||
type: Number,
|
|
||||||
},
|
|
||||||
scrollWheelZoom: {
|
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
label: translate('Allow scroll wheel zoom?'),
|
impacts: ['ui'],
|
||||||
},
|
},
|
||||||
scaleControl: {
|
captionBar: {
|
||||||
|
impacts: ['ui'],
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
label: translate('Do you want to display the scale control?'),
|
label: translate('Do you want to display a caption bar?'),
|
||||||
|
default: false,
|
||||||
|
impacts: [],
|
||||||
|
},
|
||||||
|
captionMenus: {
|
||||||
|
impacts: ['ui'],
|
||||||
|
type: Boolean,
|
||||||
|
label: translate('Do you want to display caption menus?'),
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
moreControl: {
|
color: {
|
||||||
type: Boolean,
|
impacts: ['data'],
|
||||||
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,
|
type: String,
|
||||||
label: translate('Do you want to display a panel on load?'),
|
handler: 'ColorPicker',
|
||||||
choices: [
|
label: translate('color'),
|
||||||
['none', translate('None')],
|
helpEntries: 'colorValue',
|
||||||
['caption', translate('Caption')],
|
inheritable: true,
|
||||||
['databrowser', translate('Data browser')],
|
default: 'DarkBlue',
|
||||||
['facet', translate('Facet search')],
|
},
|
||||||
],
|
dashArray: {
|
||||||
default: 'none',
|
impacts: ['data'],
|
||||||
|
type: String,
|
||||||
|
label: translate('dash array'),
|
||||||
|
helpEntries: 'dashArray',
|
||||||
|
inheritable: true,
|
||||||
|
},
|
||||||
|
datalayersControl: {
|
||||||
|
impacts: ['ui'],
|
||||||
|
type: Boolean,
|
||||||
|
nullable: true,
|
||||||
|
handler: 'DataLayersControl',
|
||||||
|
label: translate('Display the data layers control'),
|
||||||
|
default: true,
|
||||||
},
|
},
|
||||||
defaultView: {
|
defaultView: {
|
||||||
|
impacts: [], // no need to update the ui, only useful when loading the map
|
||||||
type: String,
|
type: String,
|
||||||
label: translate('Default view'),
|
label: translate('Default view'),
|
||||||
choices: [
|
choices: [
|
||||||
|
@ -50,37 +57,80 @@ export const SCHEMA = {
|
||||||
],
|
],
|
||||||
default: 'center',
|
default: 'center',
|
||||||
},
|
},
|
||||||
name: {
|
|
||||||
type: String,
|
|
||||||
label: translate('name'),
|
|
||||||
},
|
|
||||||
description: {
|
description: {
|
||||||
label: translate('description'),
|
label: translate('description'),
|
||||||
|
impacts: ['ui'],
|
||||||
type: 'Text',
|
type: 'Text',
|
||||||
helpEntries: 'textFormatting',
|
helpEntries: 'textFormatting',
|
||||||
},
|
},
|
||||||
licence: {
|
displayOnLoad: {
|
||||||
type: String,
|
type: Boolean,
|
||||||
label: translate('licence'),
|
impacts: [],
|
||||||
},
|
},
|
||||||
tilelayer: {
|
displayPopupFooter: {
|
||||||
type: Object,
|
type: Boolean,
|
||||||
|
impacts: ['ui'],
|
||||||
|
label: translate('Do you want to display popup footer?'),
|
||||||
|
default: false,
|
||||||
},
|
},
|
||||||
overlay: {
|
easing: { impacts: [], type: Boolean, default: false },
|
||||||
type: Object,
|
editinosmControl: {
|
||||||
|
impacts: ['ui'],
|
||||||
|
type: Boolean,
|
||||||
|
nullable: true,
|
||||||
|
label: translate('Display the control to open OpenStreetMap editor'),
|
||||||
|
default: null,
|
||||||
},
|
},
|
||||||
limitBounds: {
|
embedControl: {
|
||||||
type: Object,
|
impacts: ['ui'],
|
||||||
|
type: Boolean,
|
||||||
|
nullable: true,
|
||||||
|
label: translate('Display the embed control'),
|
||||||
|
default: true,
|
||||||
},
|
},
|
||||||
color: {
|
facetKey: { impacts: ['ui'], type: String },
|
||||||
|
fill: {
|
||||||
|
impacts: ['data'],
|
||||||
|
type: Boolean,
|
||||||
|
label: translate('fill'),
|
||||||
|
helpEntries: 'fill',
|
||||||
|
inheritable: true,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
fillColor: {
|
||||||
|
impacts: ['data'],
|
||||||
type: String,
|
type: String,
|
||||||
handler: 'ColorPicker',
|
handler: 'ColorPicker',
|
||||||
label: translate('color'),
|
label: translate('fill color'),
|
||||||
helpEntries: 'colorValue',
|
helpEntries: 'fillColor',
|
||||||
inheritable: true,
|
inheritable: true,
|
||||||
default: 'DarkBlue',
|
},
|
||||||
|
fillOpacity: {
|
||||||
|
impacts: ['data'],
|
||||||
|
type: Number,
|
||||||
|
min: 0.1,
|
||||||
|
max: 1,
|
||||||
|
step: 0.1,
|
||||||
|
label: translate('fill opacity'),
|
||||||
|
inheritable: true,
|
||||||
|
default: 0.3,
|
||||||
|
},
|
||||||
|
filterKey: { impacts: [], type: String },
|
||||||
|
fromZoom: {
|
||||||
|
impacts: [], // not needed
|
||||||
|
type: Number,
|
||||||
|
label: translate('From zoom'),
|
||||||
|
helpText: translate('Optional.'),
|
||||||
|
},
|
||||||
|
fullscreenControl: {
|
||||||
|
impacts: ['ui'],
|
||||||
|
type: Boolean,
|
||||||
|
nullable: true,
|
||||||
|
label: translate('Display the fullscreen control'),
|
||||||
|
default: true,
|
||||||
},
|
},
|
||||||
iconClass: {
|
iconClass: {
|
||||||
|
impacts: ['data'],
|
||||||
type: String,
|
type: String,
|
||||||
label: translate('Icon shape'),
|
label: translate('Icon shape'),
|
||||||
inheritable: true,
|
inheritable: true,
|
||||||
|
@ -92,26 +142,8 @@ export const SCHEMA = {
|
||||||
],
|
],
|
||||||
default: 'Default',
|
default: 'Default',
|
||||||
},
|
},
|
||||||
iconUrl: {
|
|
||||||
type: String,
|
|
||||||
handler: 'IconUrl',
|
|
||||||
label: translate('Icon symbol'),
|
|
||||||
inheritable: true,
|
|
||||||
// helpText: translate(
|
|
||||||
// 'Symbol can be either a unicode character or an URL. You can use feature properties as variables: ex.: with "http://myserver.org/images/{name}.png", the {name} variable will be replaced by the "name" value of each marker.'
|
|
||||||
// ),
|
|
||||||
},
|
|
||||||
smoothFactor: {
|
|
||||||
type: Number,
|
|
||||||
min: 0,
|
|
||||||
max: 10,
|
|
||||||
step: 0.5,
|
|
||||||
label: translate('Simplify'),
|
|
||||||
helpEntries: 'smoothFactor',
|
|
||||||
inheritable: true,
|
|
||||||
default: 1.0,
|
|
||||||
},
|
|
||||||
iconOpacity: {
|
iconOpacity: {
|
||||||
|
impacts: ['data'],
|
||||||
type: Number,
|
type: Number,
|
||||||
min: 0.1,
|
min: 0.1,
|
||||||
max: 1,
|
max: 1,
|
||||||
|
@ -120,132 +152,28 @@ export const SCHEMA = {
|
||||||
inheritable: true,
|
inheritable: true,
|
||||||
default: 1,
|
default: 1,
|
||||||
},
|
},
|
||||||
opacity: {
|
iconUrl: {
|
||||||
type: Number,
|
impacts: ['data'],
|
||||||
min: 0.1,
|
type: String,
|
||||||
max: 1,
|
handler: 'IconUrl',
|
||||||
step: 0.1,
|
label: translate('Icon symbol'),
|
||||||
label: translate('opacity'),
|
|
||||||
inheritable: true,
|
inheritable: true,
|
||||||
default: 0.5,
|
|
||||||
},
|
},
|
||||||
weight: {
|
inCaption: {
|
||||||
type: Number,
|
|
||||||
min: 1,
|
|
||||||
max: 20,
|
|
||||||
step: 1,
|
|
||||||
label: translate('weight'),
|
|
||||||
inheritable: true,
|
|
||||||
default: 3,
|
|
||||||
},
|
|
||||||
fill: {
|
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
label: translate('fill'),
|
impacts: ['ui'],
|
||||||
helpEntries: 'fill',
|
},
|
||||||
|
|
||||||
|
interactive: {
|
||||||
|
impacts: ['data'],
|
||||||
|
type: Boolean,
|
||||||
|
label: translate('Allow interactions'),
|
||||||
|
helpEntries: 'interactive',
|
||||||
inheritable: true,
|
inheritable: true,
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
fillColor: {
|
|
||||||
type: String,
|
|
||||||
handler: 'ColorPicker',
|
|
||||||
label: translate('fill color'),
|
|
||||||
helpEntries: 'fillColor',
|
|
||||||
inheritable: true,
|
|
||||||
},
|
|
||||||
fillOpacity: {
|
|
||||||
type: Number,
|
|
||||||
min: 0.1,
|
|
||||||
max: 1,
|
|
||||||
step: 0.1,
|
|
||||||
label: translate('fill opacity'),
|
|
||||||
inheritable: true,
|
|
||||||
default: 0.3,
|
|
||||||
},
|
|
||||||
dashArray: {
|
|
||||||
type: String,
|
|
||||||
label: translate('dash array'),
|
|
||||||
helpEntries: 'dashArray',
|
|
||||||
inheritable: true,
|
|
||||||
},
|
|
||||||
popupShape: {
|
|
||||||
type: String,
|
|
||||||
label: translate('Popup shape'),
|
|
||||||
inheritable: true,
|
|
||||||
choices: [
|
|
||||||
['Default', translate('Popup')],
|
|
||||||
['Large', translate('Popup (large)')],
|
|
||||||
['Panel', translate('Side panel')],
|
|
||||||
],
|
|
||||||
default: 'Default',
|
|
||||||
},
|
|
||||||
popupTemplate: {
|
|
||||||
type: String,
|
|
||||||
label: translate('Popup content style'),
|
|
||||||
inheritable: true,
|
|
||||||
choices: [
|
|
||||||
['Default', translate('Default')],
|
|
||||||
['Table', translate('Table')],
|
|
||||||
['GeoRSSImage', translate('GeoRSS (title + image)')],
|
|
||||||
['GeoRSSLink', translate('GeoRSS (only link)')],
|
|
||||||
['OSM', translate('OpenStreetMap')],
|
|
||||||
],
|
|
||||||
default: 'Default',
|
|
||||||
},
|
|
||||||
popupContentTemplate: {
|
|
||||||
type: 'Text',
|
|
||||||
label: translate('Popup content template'),
|
|
||||||
helpEntries: ['dynamicProperties', 'textFormatting'],
|
|
||||||
placeholder: '# {name}',
|
|
||||||
inheritable: true,
|
|
||||||
default: '# {name}\n{description}',
|
|
||||||
},
|
|
||||||
zoomTo: {
|
|
||||||
type: Number,
|
|
||||||
placeholder: translate('Inherit'),
|
|
||||||
helpEntries: 'zoomTo',
|
|
||||||
label: translate('Default zoom level'),
|
|
||||||
inheritable: true,
|
|
||||||
},
|
|
||||||
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,
|
|
||||||
},
|
|
||||||
sortKey: {
|
|
||||||
type: String,
|
|
||||||
},
|
|
||||||
labelKey: {
|
|
||||||
type: String,
|
|
||||||
helpEntries: 'labelKey',
|
|
||||||
placeholder: translate('Default: name'),
|
|
||||||
label: translate('Label key'),
|
|
||||||
inheritable: true,
|
|
||||||
},
|
|
||||||
filterKey: {
|
|
||||||
type: String,
|
|
||||||
},
|
|
||||||
facetKey: {
|
|
||||||
type: String,
|
|
||||||
},
|
|
||||||
slugKey: {
|
|
||||||
type: String,
|
|
||||||
},
|
|
||||||
showLabel: {
|
|
||||||
type: Boolean,
|
|
||||||
nullable: true,
|
|
||||||
label: translate('Display label'),
|
|
||||||
inheritable: true,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
labelDirection: {
|
labelDirection: {
|
||||||
|
impacts: ['data'],
|
||||||
type: String,
|
type: String,
|
||||||
label: translate('Label direction'),
|
label: translate('Label direction'),
|
||||||
inheritable: true,
|
inheritable: true,
|
||||||
|
@ -259,11 +187,87 @@ export const SCHEMA = {
|
||||||
default: 'auto',
|
default: 'auto',
|
||||||
},
|
},
|
||||||
labelInteractive: {
|
labelInteractive: {
|
||||||
|
impacts: ['data'],
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
label: translate('Labels are clickable'),
|
label: translate('Labels are clickable'),
|
||||||
inheritable: true,
|
inheritable: true,
|
||||||
},
|
},
|
||||||
|
labelKey: {
|
||||||
|
impacts: ['data'],
|
||||||
|
type: String,
|
||||||
|
helpEntries: 'labelKey',
|
||||||
|
placeholder: translate('Default: name'),
|
||||||
|
label: translate('Label key'),
|
||||||
|
inheritable: true,
|
||||||
|
},
|
||||||
|
licence: { impacts: ['ui'], type: String, label: translate('licence') },
|
||||||
|
limitBounds: { impacts: ['limit-bounds'], type: Object },
|
||||||
|
locateControl: {
|
||||||
|
impacts: ['ui'],
|
||||||
|
type: Boolean,
|
||||||
|
nullable: true,
|
||||||
|
label: translate('Display the locate control'),
|
||||||
|
},
|
||||||
|
longCredit: {
|
||||||
|
impacts: ['ui'],
|
||||||
|
type: 'Text',
|
||||||
|
label: translate('Long credits'),
|
||||||
|
helpEntries: ['longCredit', 'textFormatting'],
|
||||||
|
},
|
||||||
|
measureControl: {
|
||||||
|
impacts: ['ui'],
|
||||||
|
type: Boolean,
|
||||||
|
nullable: true,
|
||||||
|
label: translate('Display the measure control'),
|
||||||
|
},
|
||||||
|
miniMap: {
|
||||||
|
impacts: ['ui'],
|
||||||
|
type: Boolean,
|
||||||
|
label: translate('Do you want to display a minimap?'),
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
moreControl: {
|
||||||
|
impacts: ['ui'],
|
||||||
|
type: Boolean,
|
||||||
|
label: translate('Do you want to display the «more» control?'),
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
impacts: ['ui', 'data'],
|
||||||
|
type: String,
|
||||||
|
label: translate('name'),
|
||||||
|
},
|
||||||
|
onLoadPanel: {
|
||||||
|
impacts: [], // This is what happens during the map instantiation
|
||||||
|
type: String,
|
||||||
|
label: translate('Do you want to display a panel on load?'),
|
||||||
|
choices: [
|
||||||
|
['none', translate('None')],
|
||||||
|
['caption', translate('Caption')],
|
||||||
|
['databrowser', translate('Data browser')],
|
||||||
|
['facet', translate('Facet search')],
|
||||||
|
],
|
||||||
|
default: 'none',
|
||||||
|
},
|
||||||
|
opacity: {
|
||||||
|
impacts: ['data'],
|
||||||
|
type: Number,
|
||||||
|
min: 0.1,
|
||||||
|
max: 1,
|
||||||
|
step: 0.1,
|
||||||
|
label: translate('opacity'),
|
||||||
|
inheritable: true,
|
||||||
|
default: 0.5,
|
||||||
|
},
|
||||||
|
outlink: {
|
||||||
|
type: String,
|
||||||
|
label: translate('Link to…'),
|
||||||
|
helpEntries: 'outlink',
|
||||||
|
placeholder: 'http://...',
|
||||||
|
inheritable: true,
|
||||||
|
},
|
||||||
outlinkTarget: {
|
outlinkTarget: {
|
||||||
|
impacts: [],
|
||||||
type: String,
|
type: String,
|
||||||
label: translate('Open link in…'),
|
label: translate('Open link in…'),
|
||||||
inheritable: true,
|
inheritable: true,
|
||||||
|
@ -274,115 +278,162 @@ export const SCHEMA = {
|
||||||
['parent', translate('parent window')],
|
['parent', translate('parent window')],
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
shortCredit: {
|
overlay: { impacts: ['background'], type: Object },
|
||||||
type: String,
|
|
||||||
label: translate('Short credits'),
|
|
||||||
helpEntries: ['shortCredit', 'textFormatting'],
|
|
||||||
},
|
|
||||||
longCredit: {
|
|
||||||
type: 'Text',
|
|
||||||
label: translate('Long credits'),
|
|
||||||
helpEntries: ['longCredit', 'textFormatting'],
|
|
||||||
},
|
|
||||||
permanentCredit: {
|
permanentCredit: {
|
||||||
|
impacts: ['ui'],
|
||||||
type: 'Text',
|
type: 'Text',
|
||||||
label: translate('Permanent credits'),
|
label: translate('Permanent credits'),
|
||||||
helpEntries: ['permanentCredit', 'textFormatting'],
|
helpEntries: ['permanentCredit', 'textFormatting'],
|
||||||
},
|
},
|
||||||
permanentCreditBackground: {
|
permanentCreditBackground: {
|
||||||
|
impacts: ['ui'],
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
label: translate('Permanent credits background'),
|
label: translate('Permanent credits background'),
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
zoomControl: {
|
popupContentTemplate: {
|
||||||
|
impacts: [], // not needed
|
||||||
|
type: 'Text',
|
||||||
|
label: translate('Popup content template'),
|
||||||
|
helpEntries: ['dynamicProperties', 'textFormatting'],
|
||||||
|
placeholder: '# {name}',
|
||||||
|
inheritable: true,
|
||||||
|
default: '# {name}\n{description}',
|
||||||
|
},
|
||||||
|
popupShape: {
|
||||||
|
impacts: [], // not needed
|
||||||
|
type: String,
|
||||||
|
label: translate('Popup shape'),
|
||||||
|
inheritable: true,
|
||||||
|
choices: [
|
||||||
|
['Default', translate('Popup')],
|
||||||
|
['Large', translate('Popup (large)')],
|
||||||
|
['Panel', translate('Side panel')],
|
||||||
|
],
|
||||||
|
default: 'Default',
|
||||||
|
},
|
||||||
|
popupTemplate: {
|
||||||
|
impacts: [], // not needed
|
||||||
|
type: String,
|
||||||
|
label: translate('Popup content style'),
|
||||||
|
inheritable: true,
|
||||||
|
choices: [
|
||||||
|
['Default', translate('Default')],
|
||||||
|
['Table', translate('Table')],
|
||||||
|
['GeoRSSImage', translate('GeoRSS (title + image)')],
|
||||||
|
['GeoRSSLink', translate('GeoRSS (only link)')],
|
||||||
|
['OSM', translate('OpenStreetMap')],
|
||||||
|
],
|
||||||
|
default: 'Default',
|
||||||
|
},
|
||||||
|
remoteData: {
|
||||||
|
type: Object,
|
||||||
|
impacts: ['remote-data'],
|
||||||
|
},
|
||||||
|
scaleControl: {
|
||||||
|
impacts: ['ui'],
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
nullable: true,
|
label: translate('Do you want to display the scale control?'),
|
||||||
label: translate('Display the zoom control'),
|
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
datalayersControl: {
|
scrollWheelZoom: {
|
||||||
|
impacts: ['ui'],
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
nullable: true,
|
label: translate('Allow scroll wheel zoom?'),
|
||||||
handler: 'DataLayersControl',
|
|
||||||
label: translate('Display the data layers control'),
|
|
||||||
default: true,
|
|
||||||
},
|
},
|
||||||
searchControl: {
|
searchControl: {
|
||||||
|
impacts: ['ui'],
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
nullable: true,
|
nullable: true,
|
||||||
label: translate('Display the search control'),
|
label: translate('Display the search control'),
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
locateControl: {
|
shortCredit: {
|
||||||
|
impacts: ['ui'],
|
||||||
|
type: String,
|
||||||
|
label: translate('Short credits'),
|
||||||
|
helpEntries: ['shortCredit', 'textFormatting'],
|
||||||
|
},
|
||||||
|
showLabel: {
|
||||||
|
impacts: ['data'],
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
nullable: true,
|
nullable: true,
|
||||||
label: translate('Display the locate control'),
|
label: translate('Display label'),
|
||||||
|
inheritable: true,
|
||||||
|
default: false,
|
||||||
},
|
},
|
||||||
fullscreenControl: {
|
slideshow: { impacts: ['ui'], type: Object },
|
||||||
type: Boolean,
|
slugKey: { impacts: [], type: String },
|
||||||
nullable: true,
|
smoothFactor: {
|
||||||
label: translate('Display the fullscreen control'),
|
impacts: ['data'],
|
||||||
default: true,
|
type: Number,
|
||||||
},
|
min: 0,
|
||||||
editinosmControl: {
|
max: 10,
|
||||||
type: Boolean,
|
step: 0.5,
|
||||||
nullable: true,
|
label: translate('Simplify'),
|
||||||
label: translate('Display the control to open OpenStreetMap editor'),
|
helpEntries: 'smoothFactor',
|
||||||
default: null,
|
inheritable: true,
|
||||||
},
|
default: 1.0,
|
||||||
embedControl: {
|
|
||||||
type: Boolean,
|
|
||||||
nullable: true,
|
|
||||||
label: translate('Display the embed control'),
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
measureControl: {
|
|
||||||
type: Boolean,
|
|
||||||
nullable: true,
|
|
||||||
label: translate('Display the measure control'),
|
|
||||||
},
|
|
||||||
tilelayersControl: {
|
|
||||||
type: Boolean,
|
|
||||||
nullable: true,
|
|
||||||
label: translate('Display the tile layers control'),
|
|
||||||
},
|
},
|
||||||
|
sortKey: { impacts: ['data', 'datalayer-index'], type: String },
|
||||||
starControl: {
|
starControl: {
|
||||||
|
impacts: ['ui'],
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
nullable: true,
|
nullable: true,
|
||||||
label: translate('Display the star map button'),
|
label: translate('Display the star map button'),
|
||||||
},
|
},
|
||||||
easing: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
interactive: {
|
|
||||||
type: Boolean,
|
|
||||||
label: translate('Allow interactions'),
|
|
||||||
helpEntries: 'interactive',
|
|
||||||
inheritable: true,
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
fromZoom: {
|
|
||||||
type: Number,
|
|
||||||
label: translate('From zoom'),
|
|
||||||
helpText: translate('Optional.'),
|
|
||||||
},
|
|
||||||
toZoom: {
|
|
||||||
type: Number,
|
|
||||||
label: translate('To zoom'),
|
|
||||||
helpText: translate('Optional.'),
|
|
||||||
},
|
|
||||||
stroke: {
|
stroke: {
|
||||||
|
impacts: ['data'],
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
label: translate('stroke'),
|
label: translate('stroke'),
|
||||||
helpEntries: 'stroke',
|
helpEntries: 'stroke',
|
||||||
inheritable: true,
|
inheritable: true,
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
outlink: {
|
tilelayer: { impacts: ['background'], type: Object },
|
||||||
label: translate('Link to…'),
|
tilelayersControl: {
|
||||||
helpEntries: 'outlink',
|
impacts: ['ui'],
|
||||||
placeholder: 'http://...',
|
type: Boolean,
|
||||||
|
nullable: true,
|
||||||
|
label: translate('Display the tile layers control'),
|
||||||
|
},
|
||||||
|
toZoom: {
|
||||||
|
type: Number,
|
||||||
|
impacts: [], // not needed
|
||||||
|
label: translate('To zoom'),
|
||||||
|
helpText: translate('Optional.'),
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
type: 'String',
|
||||||
|
impacts: ['data'],
|
||||||
|
},
|
||||||
|
weight: {
|
||||||
|
impacts: ['data'],
|
||||||
|
type: Number,
|
||||||
|
min: 1,
|
||||||
|
max: 20,
|
||||||
|
step: 1,
|
||||||
|
label: translate('weight'),
|
||||||
|
inheritable: true,
|
||||||
|
default: 3,
|
||||||
|
},
|
||||||
|
zoom: {
|
||||||
|
impacts: [], // default zoom, doesn't need to be updated
|
||||||
|
type: Number,
|
||||||
|
},
|
||||||
|
zoomControl: {
|
||||||
|
impacts: ['ui'],
|
||||||
|
type: Boolean,
|
||||||
|
nullable: true,
|
||||||
|
label: translate('Display the zoom control'),
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
zoomTo: {
|
||||||
|
impacts: [], // not need to update the view
|
||||||
|
type: Number,
|
||||||
|
placeholder: translate('Inherit'),
|
||||||
|
helpEntries: 'zoomTo',
|
||||||
|
label: translate('Default zoom level'),
|
||||||
inheritable: true,
|
inheritable: true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,31 @@ export function checkId(string) {
|
||||||
return /^[A-Za-z0-9]{5}$/.test(string)
|
return /^[A-Za-z0-9]{5}$/.test(string)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute the impacts for a given list of fields.
|
||||||
|
*
|
||||||
|
* Return a set containing the impacts.
|
||||||
|
*
|
||||||
|
* @param {fields} list[fields]
|
||||||
|
* @returns Set[string]
|
||||||
|
*/
|
||||||
|
export function getImpactsFromSchema(fields) {
|
||||||
|
return fields
|
||||||
|
.map((field) => {
|
||||||
|
// remove the option prefix for fields
|
||||||
|
// And only keep the first part in case of a subfield
|
||||||
|
// (e.g "options.limitBounds.foobar" will just return "limitBounds")
|
||||||
|
return field.replace('options.', '').split('.')[0]
|
||||||
|
})
|
||||||
|
.reduce((acc, field) => {
|
||||||
|
// retrieve the "impacts" field from the schema
|
||||||
|
// and merge them together using sets
|
||||||
|
const impacts = U.SCHEMA[field]?.impacts || []
|
||||||
|
impacts.forEach((impact) => acc.add(impact))
|
||||||
|
return acc
|
||||||
|
}, new Set())
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Import DOM purify, and initialize it.
|
* Import DOM purify, and initialize it.
|
||||||
*
|
*
|
||||||
|
@ -303,5 +328,5 @@ export function template(str, data) {
|
||||||
value = value(data)
|
value = value(data)
|
||||||
}
|
}
|
||||||
return value
|
return value
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1032,7 +1032,10 @@ U.FormBuilder = L.FormBuilder.extend({
|
||||||
|
|
||||||
setter: function (field, value) {
|
setter: function (field, value) {
|
||||||
L.FormBuilder.prototype.setter.call(this, field, value)
|
L.FormBuilder.prototype.setter.call(this, field, value)
|
||||||
if (this.options.makeDirty !== false) this.obj.isDirty = true
|
if (this.options.makeDirty !== false) {
|
||||||
|
this.obj.isDirty = true
|
||||||
|
if ('render' in this.obj) this.obj.render([field], this)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
finish: function () {
|
finish: function () {
|
||||||
|
|
|
@ -69,6 +69,7 @@ U.Map = L.Map.extend({
|
||||||
this.options.zoomControl = zoomControl !== undefined ? zoomControl : true
|
this.options.zoomControl = zoomControl !== undefined ? zoomControl : true
|
||||||
this.options.fullscreenControl =
|
this.options.fullscreenControl =
|
||||||
fullscreenControl !== undefined ? fullscreenControl : true
|
fullscreenControl !== undefined ? fullscreenControl : true
|
||||||
|
|
||||||
this.datalayersFromQueryString = L.Util.queryString('datalayers')
|
this.datalayersFromQueryString = L.Util.queryString('datalayers')
|
||||||
if (this.datalayersFromQueryString) {
|
if (this.datalayersFromQueryString) {
|
||||||
this.datalayersFromQueryString = this.datalayersFromQueryString
|
this.datalayersFromQueryString = this.datalayersFromQueryString
|
||||||
|
@ -242,6 +243,42 @@ U.Map = L.Map.extend({
|
||||||
this.on('click contextmenu.show', this.closeInplaceToolbar)
|
this.on('click contextmenu.show', this.closeInplaceToolbar)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
render: function (fields) {
|
||||||
|
let impacts = U.Utils.getImpactsFromSchema(fields)
|
||||||
|
|
||||||
|
for (let impact of impacts) {
|
||||||
|
switch (impact) {
|
||||||
|
case 'ui':
|
||||||
|
this.initCaptionBar()
|
||||||
|
this.renderEditToolbar()
|
||||||
|
this.renderControls()
|
||||||
|
break
|
||||||
|
case 'data':
|
||||||
|
this.redrawVisibleDataLayers()
|
||||||
|
break
|
||||||
|
case 'datalayer-index':
|
||||||
|
this.reindexDataLayers()
|
||||||
|
break
|
||||||
|
case 'background':
|
||||||
|
this.initTileLayers()
|
||||||
|
break
|
||||||
|
case 'bounds':
|
||||||
|
this.handleLimitBounds()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
reindexDataLayers: function () {
|
||||||
|
this.eachDataLayer((datalayer) => datalayer.reindex())
|
||||||
|
},
|
||||||
|
|
||||||
|
redrawVisibleDataLayers: function () {
|
||||||
|
this.eachVisibleDataLayer((datalayer) => {
|
||||||
|
datalayer.redraw()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
setOptionsFromQueryString: function (options) {
|
setOptionsFromQueryString: function (options) {
|
||||||
// This is not an editable option
|
// This is not an editable option
|
||||||
L.Util.setFromQueryString(options, 'editMode')
|
L.Util.setFromQueryString(options, 'editMode')
|
||||||
|
@ -269,6 +306,8 @@ U.Map = L.Map.extend({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Merge the given schema with the default one
|
||||||
|
// Missing keys inside the schema are merged with the default ones.
|
||||||
overrideSchema: function (schema) {
|
overrideSchema: function (schema) {
|
||||||
for (const [key, extra] of Object.entries(schema)) {
|
for (const [key, extra] of Object.entries(schema)) {
|
||||||
U.SCHEMA[key] = L.extend({}, U.SCHEMA[key], extra)
|
U.SCHEMA[key] = L.extend({}, U.SCHEMA[key], extra)
|
||||||
|
@ -1128,13 +1167,7 @@ U.Map = L.Map.extend({
|
||||||
'options.captionBar',
|
'options.captionBar',
|
||||||
'options.captionMenus',
|
'options.captionMenus',
|
||||||
])
|
])
|
||||||
builder = new U.FormBuilder(this, UIFields, {
|
builder = new U.FormBuilder(this, UIFields)
|
||||||
callback: function () {
|
|
||||||
this.renderControls()
|
|
||||||
this.initCaptionBar()
|
|
||||||
},
|
|
||||||
callbackContext: this,
|
|
||||||
})
|
|
||||||
const controlsOptions = L.DomUtil.createFieldset(
|
const controlsOptions = L.DomUtil.createFieldset(
|
||||||
container,
|
container,
|
||||||
L._('User interface options')
|
L._('User interface options')
|
||||||
|
@ -1157,14 +1190,7 @@ U.Map = L.Map.extend({
|
||||||
'options.dashArray',
|
'options.dashArray',
|
||||||
]
|
]
|
||||||
|
|
||||||
builder = new U.FormBuilder(this, shapeOptions, {
|
builder = new U.FormBuilder(this, shapeOptions)
|
||||||
callback: function (e) {
|
|
||||||
if (this._controls.miniMap) this.renderControls()
|
|
||||||
this.eachVisibleDataLayer((datalayer) => {
|
|
||||||
datalayer.redraw()
|
|
||||||
})
|
|
||||||
},
|
|
||||||
})
|
|
||||||
const defaultShapeProperties = L.DomUtil.createFieldset(
|
const defaultShapeProperties = L.DomUtil.createFieldset(
|
||||||
container,
|
container,
|
||||||
L._('Default shape properties')
|
L._('Default shape properties')
|
||||||
|
@ -1217,14 +1243,7 @@ U.Map = L.Map.extend({
|
||||||
],
|
],
|
||||||
]
|
]
|
||||||
|
|
||||||
builder = new U.FormBuilder(this, optionsFields, {
|
builder = new U.FormBuilder(this, optionsFields)
|
||||||
callback: function (e) {
|
|
||||||
this.initCaptionBar()
|
|
||||||
if (e.helper.field === 'options.sortKey') {
|
|
||||||
this.eachDataLayer((datalayer) => datalayer.reindex())
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
const defaultProperties = L.DomUtil.createFieldset(
|
const defaultProperties = L.DomUtil.createFieldset(
|
||||||
container,
|
container,
|
||||||
L._('Default properties')
|
L._('Default properties')
|
||||||
|
@ -1242,20 +1261,7 @@ U.Map = L.Map.extend({
|
||||||
'options.labelInteractive',
|
'options.labelInteractive',
|
||||||
'options.outlinkTarget',
|
'options.outlinkTarget',
|
||||||
]
|
]
|
||||||
builder = new U.FormBuilder(this, popupFields, {
|
builder = new U.FormBuilder(this, popupFields)
|
||||||
callback: function (e) {
|
|
||||||
if (
|
|
||||||
e.helper.field === 'options.popupTemplate' ||
|
|
||||||
e.helper.field === 'options.popupContentTemplate' ||
|
|
||||||
e.helper.field === 'options.popupShape' ||
|
|
||||||
e.helper.field === 'options.outlinkTarget'
|
|
||||||
)
|
|
||||||
return
|
|
||||||
this.eachVisibleDataLayer((datalayer) => {
|
|
||||||
datalayer.redraw()
|
|
||||||
})
|
|
||||||
},
|
|
||||||
})
|
|
||||||
const popupFieldset = L.DomUtil.createFieldset(
|
const popupFieldset = L.DomUtil.createFieldset(
|
||||||
container,
|
container,
|
||||||
L._('Default interaction options')
|
L._('Default interaction options')
|
||||||
|
@ -1309,10 +1315,7 @@ U.Map = L.Map.extend({
|
||||||
container,
|
container,
|
||||||
L._('Custom background')
|
L._('Custom background')
|
||||||
)
|
)
|
||||||
builder = new U.FormBuilder(this, tilelayerFields, {
|
builder = new U.FormBuilder(this, tilelayerFields)
|
||||||
callback: this.initTileLayers,
|
|
||||||
callbackContext: this,
|
|
||||||
})
|
|
||||||
customTilelayer.appendChild(builder.build())
|
customTilelayer.appendChild(builder.build())
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -1360,10 +1363,7 @@ U.Map = L.Map.extend({
|
||||||
['options.overlay.tms', { handler: 'Switch', label: L._('TMS format') }],
|
['options.overlay.tms', { handler: 'Switch', label: L._('TMS format') }],
|
||||||
]
|
]
|
||||||
const overlay = L.DomUtil.createFieldset(container, L._('Custom overlay'))
|
const overlay = L.DomUtil.createFieldset(container, L._('Custom overlay'))
|
||||||
builder = new U.FormBuilder(this, overlayFields, {
|
builder = new U.FormBuilder(this, overlayFields)
|
||||||
callback: this.initTileLayers,
|
|
||||||
callbackContext: this,
|
|
||||||
})
|
|
||||||
overlay.appendChild(builder.build())
|
overlay.appendChild(builder.build())
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -1390,10 +1390,7 @@ U.Map = L.Map.extend({
|
||||||
{ handler: 'BlurFloatInput', placeholder: L._('max East') },
|
{ handler: 'BlurFloatInput', placeholder: L._('max East') },
|
||||||
],
|
],
|
||||||
]
|
]
|
||||||
const boundsBuilder = new U.FormBuilder(this, boundsFields, {
|
const boundsBuilder = new U.FormBuilder(this, boundsFields)
|
||||||
callback: this.handleLimitBounds,
|
|
||||||
callbackContext: this,
|
|
||||||
})
|
|
||||||
limitBounds.appendChild(boundsBuilder.build())
|
limitBounds.appendChild(boundsBuilder.build())
|
||||||
const boundsButtons = L.DomUtil.create('div', 'button-bar half', limitBounds)
|
const boundsButtons = L.DomUtil.create('div', 'button-bar half', limitBounds)
|
||||||
L.DomUtil.createButton(
|
L.DomUtil.createButton(
|
||||||
|
@ -1454,7 +1451,6 @@ U.Map = L.Map.extend({
|
||||||
]
|
]
|
||||||
const slideshowHandler = function () {
|
const slideshowHandler = function () {
|
||||||
this.slideshow.setOptions(this.options.slideshow)
|
this.slideshow.setOptions(this.options.slideshow)
|
||||||
this.renderControls()
|
|
||||||
}
|
}
|
||||||
const slideshowBuilder = new U.FormBuilder(this, slideshowFields, {
|
const slideshowBuilder = new U.FormBuilder(this, slideshowFields, {
|
||||||
callback: slideshowHandler,
|
callback: slideshowHandler,
|
||||||
|
@ -1472,10 +1468,7 @@ U.Map = L.Map.extend({
|
||||||
'options.permanentCredit',
|
'options.permanentCredit',
|
||||||
'options.permanentCreditBackground',
|
'options.permanentCreditBackground',
|
||||||
]
|
]
|
||||||
const creditsBuilder = new U.FormBuilder(this, creditsFields, {
|
const creditsBuilder = new U.FormBuilder(this, creditsFields)
|
||||||
callback: this.renderControls,
|
|
||||||
callbackContext: this,
|
|
||||||
})
|
|
||||||
credits.appendChild(creditsBuilder.build())
|
credits.appendChild(creditsBuilder.build())
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -254,16 +254,18 @@ U.Layer.Choropleth = L.FeatureGroup.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
onEdit: function (field, builder) {
|
onEdit: function (field, builder) {
|
||||||
|
// Only compute the breaks if we're dealing with choropleth
|
||||||
|
if (!field.startsWith('options.choropleth')) return
|
||||||
// If user touches the breaks, then force manual mode
|
// If user touches the breaks, then force manual mode
|
||||||
if (field === 'options.choropleth.breaks') {
|
if (field === 'options.choropleth.breaks') {
|
||||||
this.datalayer.options.choropleth.mode = 'manual'
|
this.datalayer.options.choropleth.mode = 'manual'
|
||||||
builder.helpers['options.choropleth.mode'].fetch()
|
if (builder) builder.helpers['options.choropleth.mode'].fetch()
|
||||||
}
|
}
|
||||||
this.computeBreaks()
|
this.computeBreaks()
|
||||||
// If user changes the mode or the number of classes,
|
// If user changes the mode or the number of classes,
|
||||||
// then update the breaks input value
|
// then update the breaks input value
|
||||||
if (field === 'options.choropleth.mode' || field === 'options.choropleth.classes') {
|
if (field === 'options.choropleth.mode' || field === 'options.choropleth.classes') {
|
||||||
builder.helpers['options.choropleth.breaks'].fetch()
|
if (builder) builder.helpers['options.choropleth.breaks'].fetch()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -594,6 +596,31 @@ U.DataLayer = L.Evented.extend({
|
||||||
if (this.autoLoaded()) this.map.on('zoomend', this.onZoomEnd, this)
|
if (this.autoLoaded()) this.map.on('zoomend', this.onZoomEnd, this)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
render: function (fields, builder) {
|
||||||
|
let impacts = U.Utils.getImpactsFromSchema(fields)
|
||||||
|
|
||||||
|
for (let impact of impacts) {
|
||||||
|
switch (impact) {
|
||||||
|
case 'ui':
|
||||||
|
this.map.updateDatalayersControl()
|
||||||
|
break
|
||||||
|
case 'data':
|
||||||
|
if (fields.includes('options.type')) {
|
||||||
|
this.resetLayer()
|
||||||
|
}
|
||||||
|
this.hide()
|
||||||
|
fields.forEach((field) => {
|
||||||
|
this.layer.onEdit(field, builder)
|
||||||
|
})
|
||||||
|
this.show()
|
||||||
|
break
|
||||||
|
case 'remote-data':
|
||||||
|
this.fetchRemoteData()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
onMoveEnd: function (e) {
|
onMoveEnd: function (e) {
|
||||||
if (this.isRemoteLayer() && this.showAtZoom()) this.fetchRemoteData()
|
if (this.isRemoteLayer() && this.showAtZoom()) this.fetchRemoteData()
|
||||||
},
|
},
|
||||||
|
@ -1189,29 +1216,18 @@ U.DataLayer = L.Evented.extend({
|
||||||
const title = L.DomUtil.add('h3', '', container, L._('Layer properties'))
|
const title = L.DomUtil.add('h3', '', container, L._('Layer properties'))
|
||||||
let builder = new U.FormBuilder(this, metadataFields, {
|
let builder = new U.FormBuilder(this, metadataFields, {
|
||||||
callback: function (e) {
|
callback: function (e) {
|
||||||
this.map.updateDatalayersControl()
|
|
||||||
if (e.helper.field === 'options.type') {
|
if (e.helper.field === 'options.type') {
|
||||||
this.resetLayer()
|
|
||||||
this.edit()
|
this.edit()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
container.appendChild(builder.build())
|
container.appendChild(builder.build())
|
||||||
|
|
||||||
const redrawCallback = function (e) {
|
|
||||||
const field = e.helper.field,
|
|
||||||
builder = e.helper.builder
|
|
||||||
this.hide()
|
|
||||||
this.layer.onEdit(field, builder)
|
|
||||||
this.show()
|
|
||||||
}
|
|
||||||
|
|
||||||
const layerOptions = this.layer.getEditableOptions()
|
const layerOptions = this.layer.getEditableOptions()
|
||||||
|
|
||||||
if (layerOptions.length) {
|
if (layerOptions.length) {
|
||||||
builder = new U.FormBuilder(this, layerOptions, {
|
builder = new U.FormBuilder(this, layerOptions, {
|
||||||
id: 'datalayer-layer-properties',
|
id: 'datalayer-layer-properties',
|
||||||
callback: redrawCallback,
|
|
||||||
})
|
})
|
||||||
const layerProperties = L.DomUtil.createFieldset(
|
const layerProperties = L.DomUtil.createFieldset(
|
||||||
container,
|
container,
|
||||||
|
@ -1235,7 +1251,6 @@ U.DataLayer = L.Evented.extend({
|
||||||
|
|
||||||
builder = new U.FormBuilder(this, shapeOptions, {
|
builder = new U.FormBuilder(this, shapeOptions, {
|
||||||
id: 'datalayer-advanced-properties',
|
id: 'datalayer-advanced-properties',
|
||||||
callback: redrawCallback,
|
|
||||||
})
|
})
|
||||||
const shapeProperties = L.DomUtil.createFieldset(container, L._('Shape properties'))
|
const shapeProperties = L.DomUtil.createFieldset(container, L._('Shape properties'))
|
||||||
shapeProperties.appendChild(builder.build())
|
shapeProperties.appendChild(builder.build())
|
||||||
|
@ -1251,7 +1266,6 @@ U.DataLayer = L.Evented.extend({
|
||||||
|
|
||||||
builder = new U.FormBuilder(this, optionsFields, {
|
builder = new U.FormBuilder(this, optionsFields, {
|
||||||
id: 'datalayer-advanced-properties',
|
id: 'datalayer-advanced-properties',
|
||||||
callback: redrawCallback,
|
|
||||||
})
|
})
|
||||||
const advancedProperties = L.DomUtil.createFieldset(
|
const advancedProperties = L.DomUtil.createFieldset(
|
||||||
container,
|
container,
|
||||||
|
@ -1269,7 +1283,7 @@ U.DataLayer = L.Evented.extend({
|
||||||
'options.outlinkTarget',
|
'options.outlinkTarget',
|
||||||
'options.interactive',
|
'options.interactive',
|
||||||
]
|
]
|
||||||
builder = new U.FormBuilder(this, popupFields, { callback: redrawCallback })
|
builder = new U.FormBuilder(this, popupFields)
|
||||||
const popupFieldset = L.DomUtil.createFieldset(
|
const popupFieldset = L.DomUtil.createFieldset(
|
||||||
container,
|
container,
|
||||||
L._('Interaction options')
|
L._('Interaction options')
|
||||||
|
|
Loading…
Reference in a new issue