umap-project 3.0.6__py3-none-any.whl → 3.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of umap-project might be problematic. Click here for more details.

Files changed (142) hide show
  1. umap/__init__.py +1 -1
  2. umap/forms.py +1 -1
  3. umap/locale/br/LC_MESSAGES/django.mo +0 -0
  4. umap/locale/br/LC_MESSAGES/django.po +219 -72
  5. umap/locale/ca/LC_MESSAGES/django.mo +0 -0
  6. umap/locale/ca/LC_MESSAGES/django.po +286 -95
  7. umap/locale/cs_CZ/LC_MESSAGES/django.mo +0 -0
  8. umap/locale/cs_CZ/LC_MESSAGES/django.po +211 -65
  9. umap/locale/da/LC_MESSAGES/django.mo +0 -0
  10. umap/locale/da/LC_MESSAGES/django.po +394 -202
  11. umap/locale/de/LC_MESSAGES/django.mo +0 -0
  12. umap/locale/de/LC_MESSAGES/django.po +146 -75
  13. umap/locale/el/LC_MESSAGES/django.mo +0 -0
  14. umap/locale/el/LC_MESSAGES/django.po +125 -59
  15. umap/locale/en/LC_MESSAGES/django.po +124 -58
  16. umap/locale/es/LC_MESSAGES/django.mo +0 -0
  17. umap/locale/es/LC_MESSAGES/django.po +125 -59
  18. umap/locale/et/LC_MESSAGES/django.mo +0 -0
  19. umap/locale/et/LC_MESSAGES/django.po +210 -64
  20. umap/locale/eu/LC_MESSAGES/django.mo +0 -0
  21. umap/locale/eu/LC_MESSAGES/django.po +212 -65
  22. umap/locale/fa_IR/LC_MESSAGES/django.mo +0 -0
  23. umap/locale/fa_IR/LC_MESSAGES/django.po +286 -95
  24. umap/locale/fr/LC_MESSAGES/django.mo +0 -0
  25. umap/locale/fr/LC_MESSAGES/django.po +125 -59
  26. umap/locale/gl/LC_MESSAGES/django.mo +0 -0
  27. umap/locale/gl/LC_MESSAGES/django.po +212 -66
  28. umap/locale/hu/LC_MESSAGES/django.mo +0 -0
  29. umap/locale/hu/LC_MESSAGES/django.po +148 -78
  30. umap/locale/is/LC_MESSAGES/django.mo +0 -0
  31. umap/locale/is/LC_MESSAGES/django.po +130 -60
  32. umap/locale/it/LC_MESSAGES/django.mo +0 -0
  33. umap/locale/it/LC_MESSAGES/django.po +125 -59
  34. umap/locale/ms/LC_MESSAGES/django.mo +0 -0
  35. umap/locale/ms/LC_MESSAGES/django.po +289 -98
  36. umap/locale/nl/LC_MESSAGES/django.mo +0 -0
  37. umap/locale/nl/LC_MESSAGES/django.po +128 -61
  38. umap/locale/pl/LC_MESSAGES/django.mo +0 -0
  39. umap/locale/pl/LC_MESSAGES/django.po +287 -96
  40. umap/locale/pt/LC_MESSAGES/django.mo +0 -0
  41. umap/locale/pt/LC_MESSAGES/django.po +211 -65
  42. umap/locale/zh_TW/LC_MESSAGES/django.mo +0 -0
  43. umap/locale/zh_TW/LC_MESSAGES/django.po +212 -66
  44. umap/management/commands/migrate_to_S3.py +42 -20
  45. umap/management/commands/purge_old_versions.py +63 -0
  46. umap/management/commands/switch_user.py +52 -0
  47. umap/managers.py +29 -2
  48. umap/middleware.py +1 -1
  49. umap/migrations/0028_map_is_template.py +21 -0
  50. umap/models.py +14 -4
  51. umap/settings/base.py +22 -0
  52. umap/static/umap/base.css +4 -2
  53. umap/static/umap/content.css +1 -1
  54. umap/static/umap/css/dialog.css +5 -2
  55. umap/static/umap/css/form.css +19 -12
  56. umap/static/umap/css/icon.css +6 -0
  57. umap/static/umap/css/importers.css +4 -0
  58. umap/static/umap/css/panel.css +2 -0
  59. umap/static/umap/img/16-white.svg +5 -1
  60. umap/static/umap/img/16.svg +1 -1
  61. umap/static/umap/img/24-white.svg +3 -2
  62. umap/static/umap/img/24.svg +3 -4
  63. umap/static/umap/img/importers/opendata.svg +1 -0
  64. umap/static/umap/img/source/16-white.svg +8 -4
  65. umap/static/umap/img/source/16.svg +1 -1
  66. umap/static/umap/img/source/24-white.svg +5 -4
  67. umap/static/umap/img/source/24.svg +5 -6
  68. umap/static/umap/js/components/modal.js +27 -0
  69. umap/static/umap/js/modules/caption.js +4 -4
  70. umap/static/umap/js/modules/data/features.js +40 -4
  71. umap/static/umap/js/modules/data/layer.js +208 -138
  72. umap/static/umap/js/modules/form/builder.js +6 -14
  73. umap/static/umap/js/modules/form/fields.js +2 -2
  74. umap/static/umap/js/modules/help.js +11 -3
  75. umap/static/umap/js/modules/importer.js +7 -4
  76. umap/static/umap/js/modules/importers/opendata.js +142 -0
  77. umap/static/umap/js/modules/permissions.js +3 -3
  78. umap/static/umap/js/modules/rendering/controls.js +34 -2
  79. umap/static/umap/js/modules/rendering/icon.js +2 -2
  80. umap/static/umap/js/modules/rendering/layers/base.js +1 -1
  81. umap/static/umap/js/modules/rendering/layers/classified.js +55 -49
  82. umap/static/umap/js/modules/rendering/layers/cluster.js +16 -9
  83. umap/static/umap/js/modules/rendering/layers/heat.js +13 -11
  84. umap/static/umap/js/modules/rendering/map.js +5 -0
  85. umap/static/umap/js/modules/rendering/ui.js +23 -0
  86. umap/static/umap/js/modules/rules.js +24 -23
  87. umap/static/umap/js/modules/schema.js +60 -4
  88. umap/static/umap/js/modules/sync/updaters.js +7 -3
  89. umap/static/umap/js/modules/tableeditor.js +7 -30
  90. umap/static/umap/js/modules/templates.js +122 -0
  91. umap/static/umap/js/modules/ui/bar.js +13 -3
  92. umap/static/umap/js/modules/ui/panel.js +1 -1
  93. umap/static/umap/js/modules/umap.js +28 -13
  94. umap/static/umap/js/umap.controls.js +11 -4
  95. umap/static/umap/locale/br.js +51 -18
  96. umap/static/umap/locale/br.json +51 -18
  97. umap/static/umap/locale/da.js +343 -310
  98. umap/static/umap/locale/da.json +343 -310
  99. umap/static/umap/locale/de.js +40 -7
  100. umap/static/umap/locale/de.json +40 -7
  101. umap/static/umap/locale/el.js +31 -4
  102. umap/static/umap/locale/el.json +31 -4
  103. umap/static/umap/locale/en.js +33 -1
  104. umap/static/umap/locale/en.json +33 -1
  105. umap/static/umap/locale/es.js +37 -4
  106. umap/static/umap/locale/es.json +37 -4
  107. umap/static/umap/locale/fr.js +34 -1
  108. umap/static/umap/locale/fr.json +34 -1
  109. umap/static/umap/locale/hu.js +44 -17
  110. umap/static/umap/locale/hu.json +44 -17
  111. umap/static/umap/locale/it.js +74 -41
  112. umap/static/umap/locale/it.json +74 -41
  113. umap/static/umap/locale/nl.js +42 -9
  114. umap/static/umap/locale/nl.json +42 -9
  115. umap/static/umap/map.css +3 -23
  116. umap/static/umap/vendors/textpath/leaflet.textpath.js +184 -0
  117. umap/storage/fs.py +19 -9
  118. umap/templates/umap/dashboard_menu.html +5 -0
  119. umap/templates/umap/design_system.html +9 -0
  120. umap/templates/umap/js.html +3 -0
  121. umap/templates/umap/map_list.html +2 -2
  122. umap/templates/umap/map_table.html +18 -18
  123. umap/templates/umap/user_dashboard.html +9 -58
  124. umap/templates/umap/user_map_table.html +36 -0
  125. umap/templates/umap/user_templates.html +19 -0
  126. umap/templatetags/umap_tags.py +5 -0
  127. umap/tests/integration/test_conditional_rules.py +57 -0
  128. umap/tests/integration/test_datalayer.py +16 -9
  129. umap/tests/integration/test_edit_marker.py +11 -0
  130. umap/tests/integration/test_tableeditor.py +42 -7
  131. umap/tests/integration/test_templates.py +44 -0
  132. umap/tests/test_dashboard.py +19 -0
  133. umap/tests/test_purge_old_versions.py +91 -0
  134. umap/tests/test_switch_user.py +31 -0
  135. umap/tests/test_views.py +67 -0
  136. umap/urls.py +7 -1
  137. umap/views.py +64 -18
  138. {umap_project-3.0.6.dist-info → umap_project-3.1.0.dist-info}/METADATA +14 -14
  139. {umap_project-3.0.6.dist-info → umap_project-3.1.0.dist-info}/RECORD +142 -129
  140. {umap_project-3.0.6.dist-info → umap_project-3.1.0.dist-info}/WHEEL +0 -0
  141. {umap_project-3.0.6.dist-info → umap_project-3.1.0.dist-info}/entry_points.txt +0 -0
  142. {umap_project-3.0.6.dist-info → umap_project-3.1.0.dist-info}/licenses/LICENSE +0 -0
@@ -20,6 +20,7 @@ import * as Schema from '../schema.js'
20
20
  import TableEditor from '../tableeditor.js'
21
21
  import * as Utils from '../utils.js'
22
22
  import { LineString, Point, Polygon } from './features.js'
23
+ import Rules from '../rules.js'
23
24
 
24
25
  export const LAYER_TYPES = [
25
26
  DefaultLayer,
@@ -49,7 +50,7 @@ export class DataLayer {
49
50
  this.pane = this._leafletMap.createPane(`datalayer${stamp(this)}`, this.parentPane)
50
51
  // FIXME: should be on layer
51
52
  this.renderer = L.svg({ pane: this.pane })
52
- this.defaultOptions = {
53
+ this.defaultProperties = {
53
54
  displayOnLoad: true,
54
55
  inCaption: true,
55
56
  browsable: true,
@@ -62,27 +63,27 @@ export class DataLayer {
62
63
  delete data._referenceVersion
63
64
  data.id = data.id || crypto.randomUUID()
64
65
 
65
- this.setOptions(data)
66
+ this.setProperties(data)
66
67
  this.pane.dataset.id = this.id
67
- if (this.options.rank === undefined) {
68
- this.options.rank = this._umap.datalayers.count()
68
+ if (this.properties.rank === undefined) {
69
+ this.properties.rank = this._umap.datalayers.count()
69
70
  }
70
71
 
71
- if (!Utils.isObject(this.options.remoteData)) {
72
- this.options.remoteData = {}
72
+ if (!Utils.isObject(this.properties.remoteData)) {
73
+ this.properties.remoteData = {}
73
74
  }
74
75
  // Retrocompat
75
- if (this.options.remoteData?.from) {
76
- this.options.fromZoom = this.options.remoteData.from
77
- delete this.options.remoteData.from
76
+ if (this.properties.remoteData?.from) {
77
+ this.properties.fromZoom = this.properties.remoteData.from
78
+ delete this.properties.remoteData.from
78
79
  }
79
- if (this.options.remoteData?.to) {
80
- this.options.toZoom = this.options.remoteData.to
81
- delete this.options.remoteData.to
80
+ if (this.properties.remoteData?.to) {
81
+ this.properties.toZoom = this.properties.remoteData.to
82
+ delete this.properties.remoteData.to
82
83
  }
83
- this.backupOptions()
84
84
  this.connectToMap()
85
85
  this.permissions = new DataLayerPermissions(this._umap, this)
86
+ this.rules = new Rules(umap, this)
86
87
 
87
88
  this._needsFetch = this.createdOnServer || this.isRemoteLayer()
88
89
  if (!this.createdOnServer) {
@@ -96,7 +97,7 @@ export class DataLayer {
96
97
  }
97
98
 
98
99
  get id() {
99
- return this.options.id
100
+ return this.properties.id
100
101
  }
101
102
 
102
103
  get createdOnServer() {
@@ -129,14 +130,14 @@ export class DataLayer {
129
130
  // Make sure we always have a valid rank. Undefined rank may happen
130
131
  // after importing an old umap backup, and not touching the layers
131
132
  // after that.
132
- if (this.options.rank === undefined) {
133
- this.options.rank = this.getDOMOrder()
133
+ if (this.properties.rank === undefined) {
134
+ this.properties.rank = this.getDOMOrder()
134
135
  }
135
- return this.options.rank
136
+ return this.properties.rank
136
137
  }
137
138
 
138
139
  set rank(value) {
139
- this.options.rank = value
140
+ this.properties.rank = value
140
141
  }
141
142
 
142
143
  getSyncMetadata() {
@@ -159,7 +160,7 @@ export class DataLayer {
159
160
  this._umap.onDataLayersChanged()
160
161
  break
161
162
  case 'data':
162
- if (fields.includes('options.type')) {
163
+ if (fields.includes('properties.type')) {
163
164
  this.resetLayer()
164
165
  }
165
166
  for (const field of fields) {
@@ -205,11 +206,11 @@ export class DataLayer {
205
206
  }
206
207
 
207
208
  autoLoaded() {
208
- if (!this._umap.datalayersFromQueryString) return this.options.displayOnLoad
209
+ if (!this._umap.datalayersFromQueryString) return this.properties.displayOnLoad
209
210
  const datalayerIds = this._umap.datalayersFromQueryString
210
211
  let loadMe = datalayerIds.includes(this.id.toString())
211
- if (this.options.old_id) {
212
- loadMe = loadMe || datalayerIds.includes(this.options.old_id.toString())
212
+ if (this.properties.old_id) {
213
+ loadMe = loadMe || datalayerIds.includes(this.properties.old_id.toString())
213
214
  }
214
215
  return loadMe
215
216
  }
@@ -236,7 +237,7 @@ export class DataLayer {
236
237
  // Only reset if type is defined (undefined is the default) and different from current type
237
238
  if (
238
239
  this.layer &&
239
- (!this.options.type || this.options.type === this.layer.getType()) &&
240
+ (!this.properties.type || this.properties.type === this.layer.getType()) &&
240
241
  !force
241
242
  ) {
242
243
  return
@@ -245,7 +246,7 @@ export class DataLayer {
245
246
  if (this.layer) this.layer.clearLayers()
246
247
  // delete this.layer?
247
248
  if (visible) this._leafletMap.removeLayer(this.layer)
248
- const Class = LAYER_MAP[this.options.type] || DefaultLayer
249
+ const Class = LAYER_MAP[this.properties.type] || DefaultLayer
249
250
  this.layer = new Class(this)
250
251
  // Rendering layer changed, so let's force reset the feature rendering too.
251
252
  this.eachFeature((feature) => feature.makeUI())
@@ -273,7 +274,6 @@ export class DataLayer {
273
274
  // In case of maps pre 1.0 still around
274
275
  delete geojson._storage
275
276
  await this.fromUmapGeoJSON(geojson)
276
- this.backupOptions()
277
277
  this._loading = false
278
278
  }
279
279
  }
@@ -300,7 +300,7 @@ export class DataLayer {
300
300
 
301
301
  async fromUmapGeoJSON(geojson) {
302
302
  if (geojson._storage) geojson._umap_options = geojson._storage // Retrocompat
303
- if (geojson._umap_options) this.setOptions(geojson._umap_options)
303
+ if (geojson._umap_options) this.setProperties(geojson._umap_options)
304
304
  if (this.isRemoteLayer()) {
305
305
  await this.fetchRemoteData()
306
306
  } else {
@@ -321,14 +321,14 @@ export class DataLayer {
321
321
  }
322
322
 
323
323
  showAtZoom() {
324
- const from = Number.parseInt(this.options.fromZoom, 10)
325
- const to = Number.parseInt(this.options.toZoom, 10)
324
+ const from = Number.parseInt(this.properties.fromZoom, 10)
325
+ const to = Number.parseInt(this.properties.toZoom, 10)
326
326
  const zoom = this._leafletMap.getZoom()
327
327
  return !((!Number.isNaN(from) && zoom < from) || (!Number.isNaN(to) && zoom > to))
328
328
  }
329
329
 
330
330
  hasDynamicData() {
331
- return this.isRemoteLayer() && Boolean(this.options.remoteData?.dynamic)
331
+ return this.isRemoteLayer() && Boolean(this.properties.remoteData?.dynamic)
332
332
  }
333
333
 
334
334
  async getUrl(url, initialUrl) {
@@ -352,15 +352,15 @@ export class DataLayer {
352
352
  if (!this.hasDynamicData() && this.isLoaded() && !force) return
353
353
  if (!this.isVisible()) return
354
354
  // Keep non proxied url for later use in Alert.
355
- const remoteUrl = this._umap.renderUrl(this.options.remoteData.url)
355
+ const remoteUrl = this._umap.renderUrl(this.properties.remoteData.url)
356
356
  let url = remoteUrl
357
- if (this.options.remoteData.proxy) {
358
- url = this._umap.proxyUrl(url, this.options.remoteData.ttl)
357
+ if (this.properties.remoteData.proxy) {
358
+ url = this._umap.proxyUrl(url, this.properties.remoteData.ttl)
359
359
  }
360
360
  return await this.getUrl(url, remoteUrl).then((raw) => {
361
361
  this.clear(false)
362
362
  return this._umap.formatter
363
- .parse(raw, this.options.remoteData.format)
363
+ .parse(raw, this.properties.remoteData.format)
364
364
  .then((geojson) => this.fromGeoJSON(geojson, false))
365
365
  .catch((error) => {
366
366
  console.debug(error)
@@ -378,22 +378,14 @@ export class DataLayer {
378
378
  return !this._needsFetch
379
379
  }
380
380
 
381
- backupOptions() {
382
- this._backupOptions = Utils.CopyJSON(this.options)
381
+ setProperties(properties) {
382
+ delete properties.geojson
383
+ this.properties = Utils.CopyJSON(this.defaultProperties) // Start from fresh.
384
+ this.updateProperties(properties)
383
385
  }
384
386
 
385
- resetOptions() {
386
- this.options = Utils.CopyJSON(this._backupOptions)
387
- }
388
-
389
- setOptions(options) {
390
- delete options.geojson
391
- this.options = Utils.CopyJSON(this.defaultOptions) // Start from fresh.
392
- this.updateOptions(options)
393
- }
394
-
395
- updateOptions(options) {
396
- this.options = Object.assign(this.options, options)
387
+ updateProperties(properties) {
388
+ this.properties = Object.assign(this.properties, properties)
397
389
  this.resetLayer()
398
390
  }
399
391
 
@@ -414,11 +406,11 @@ export class DataLayer {
414
406
  }
415
407
 
416
408
  isRemoteLayer() {
417
- return Boolean(this.options.remoteData?.url && this.options.remoteData.format)
409
+ return Boolean(this.properties.remoteData?.url && this.properties.remoteData.format)
418
410
  }
419
411
 
420
412
  isClustered() {
421
- return this.options.type === 'Cluster'
413
+ return this.properties.type === 'Cluster'
422
414
  }
423
415
 
424
416
  showFeature(feature) {
@@ -473,11 +465,67 @@ export class DataLayer {
473
465
  }
474
466
  }
475
467
 
468
+ checkIndexForProperty(name) {
469
+ for (const feature of Object.values(this._features)) {
470
+ if (name in feature.properties) {
471
+ this.indexProperty(name)
472
+ return
473
+ }
474
+ }
475
+ this.deindexProperty(name)
476
+ }
477
+
476
478
  deindexProperty(name) {
477
479
  const idx = this._propertiesIndex.indexOf(name)
478
480
  if (idx !== -1) this._propertiesIndex.splice(idx, 1)
479
481
  }
480
482
 
483
+ renameProperty(oldName, newName) {
484
+ this.sync.startBatch()
485
+ this.eachFeature((feature) => {
486
+ feature.renameProperty(oldName, newName)
487
+ })
488
+ this.sync.commitBatch()
489
+ this.deindexProperty(oldName)
490
+ this.indexProperty(newName)
491
+ }
492
+
493
+ deleteProperty(property) {
494
+ this.sync.startBatch()
495
+ this.eachFeature((feature) => {
496
+ feature.deleteProperty(property)
497
+ })
498
+ this.sync.commitBatch()
499
+ this.deindexProperty(property)
500
+ }
501
+
502
+ addProperty() {
503
+ let resolve = undefined
504
+ const promise = new Promise((r) => {
505
+ resolve = r
506
+ })
507
+ this._umap.dialog
508
+ .prompt(translate('Please enter the name of the property'))
509
+ .then(({ prompt }) => {
510
+ if (!prompt || !this.validateName(prompt)) return
511
+ this.indexProperty(prompt)
512
+ resolve()
513
+ })
514
+ return promise
515
+ }
516
+
517
+ validateName(name) {
518
+ if (name.includes('.')) {
519
+ Alert.error(translate('Name “{name}” should not contain a dot.', { name }))
520
+ return false
521
+ }
522
+ if (this.allProperties().includes(name)) {
523
+ Alert.error(translate('This name already exists: “{name}”', { name }))
524
+ return false
525
+ }
526
+ return true
527
+ }
528
+
481
529
  allProperties() {
482
530
  return this._propertiesIndex
483
531
  }
@@ -502,7 +550,7 @@ export class DataLayer {
502
550
  }
503
551
 
504
552
  sortFeatures(collection) {
505
- const sortKeys = this.getOption('sortKey') || U.DEFAULT_LABEL_KEY
553
+ const sortKeys = this.getProperty('sortKey') || U.DEFAULT_LABEL_KEY
506
554
  return Utils.sortFeatures(collection, sortKeys, U.lang)
507
555
  }
508
556
 
@@ -615,7 +663,7 @@ export class DataLayer {
615
663
  }
616
664
 
617
665
  getColor() {
618
- return this.options.color || this._umap.getProperty('color')
666
+ return this.properties.color || this._umap.getProperty('color')
619
667
  }
620
668
 
621
669
  getDeleteUrl() {
@@ -672,11 +720,11 @@ export class DataLayer {
672
720
  }
673
721
 
674
722
  clone() {
675
- const options = Utils.CopyJSON(this.options)
676
- options.name = translate('Clone of {name}', { name: this.options.name })
677
- delete options.id
723
+ const properties = Utils.CopyJSON(this.properties)
724
+ properties.name = translate('Clone of {name}', { name: this.properties.name })
725
+ delete properties.id
678
726
  const geojson = Utils.CopyJSON(this._geojson)
679
- const datalayer = this._umap.createDirtyDataLayer(options)
727
+ const datalayer = this._umap.createDirtyDataLayer(properties)
680
728
  datalayer.fromGeoJSON(geojson)
681
729
  return datalayer
682
730
  }
@@ -692,19 +740,19 @@ export class DataLayer {
692
740
  }
693
741
  const container = DomUtil.create('div', 'umap-layer-properties-container')
694
742
  const metadataFields = [
695
- 'options.name',
696
- 'options.description',
743
+ 'properties.name',
744
+ 'properties.description',
697
745
  [
698
- 'options.type',
746
+ 'properties.type',
699
747
  { handler: 'LayerTypeChooser', label: translate('Type of layer') },
700
748
  ],
701
- 'options.labelKey',
749
+ 'properties.labelKey',
702
750
  [
703
- 'options.displayOnLoad',
751
+ 'properties.displayOnLoad',
704
752
  { label: translate('Display on load'), handler: 'Switch' },
705
753
  ],
706
754
  [
707
- 'options.browsable',
755
+ 'properties.browsable',
708
756
  {
709
757
  label: translate('Data is browsable'),
710
758
  handler: 'Switch',
@@ -712,7 +760,7 @@ export class DataLayer {
712
760
  },
713
761
  ],
714
762
  [
715
- 'options.inCaption',
763
+ 'properties.inCaption',
716
764
  {
717
765
  label: translate('Show this layer in the caption'),
718
766
  handler: 'Switch',
@@ -723,16 +771,16 @@ export class DataLayer {
723
771
  let builder = new MutatingForm(this, metadataFields)
724
772
  builder.on('set', ({ detail }) => {
725
773
  this._umap.onDataLayersChanged()
726
- if (detail.helper.field === 'options.type') {
774
+ if (detail.helper.field === 'properties.type') {
727
775
  this.edit()
728
776
  }
729
777
  })
730
778
  container.appendChild(builder.build())
731
779
 
732
- const layerOptions = this.layer.getEditableOptions()
780
+ const layerFields = this.layer.getEditableProperties()
733
781
 
734
- if (layerOptions.length) {
735
- builder = new MutatingForm(this, layerOptions, {
782
+ if (layerFields.length) {
783
+ builder = new MutatingForm(this, layerFields, {
736
784
  id: 'datalayer-layer-properties',
737
785
  })
738
786
  const layerProperties = DomUtil.createFieldset(
@@ -742,60 +790,60 @@ export class DataLayer {
742
790
  layerProperties.appendChild(builder.build())
743
791
  }
744
792
 
745
- const shapeOptions = [
746
- 'options.color',
747
- 'options.iconClass',
748
- 'options.iconUrl',
749
- 'options.iconOpacity',
750
- 'options.opacity',
751
- 'options.stroke',
752
- 'options.weight',
753
- 'options.fill',
754
- 'options.fillColor',
755
- 'options.fillOpacity',
793
+ const shapeFields = [
794
+ 'properties.color',
795
+ 'properties.iconClass',
796
+ 'properties.iconUrl',
797
+ 'properties.iconOpacity',
798
+ 'properties.opacity',
799
+ 'properties.stroke',
800
+ 'properties.weight',
801
+ 'properties.fill',
802
+ 'properties.fillColor',
803
+ 'properties.fillOpacity',
756
804
  ]
757
805
 
758
- builder = new MutatingForm(this, shapeOptions, {
806
+ builder = new MutatingForm(this, shapeFields, {
759
807
  id: 'datalayer-advanced-properties',
760
808
  })
761
- const shapeProperties = DomUtil.createFieldset(
809
+ const shapeFieldset = DomUtil.createFieldset(
762
810
  container,
763
811
  translate('Shape properties')
764
812
  )
765
- shapeProperties.appendChild(builder.build())
766
-
767
- const optionsFields = [
768
- 'options.smoothFactor',
769
- 'options.dashArray',
770
- 'options.zoomTo',
771
- 'options.fromZoom',
772
- 'options.toZoom',
773
- 'options.sortKey',
813
+ shapeFieldset.appendChild(builder.build())
814
+
815
+ const advancedFields = [
816
+ 'properties.smoothFactor',
817
+ 'properties.dashArray',
818
+ 'properties.zoomTo',
819
+ 'properties.fromZoom',
820
+ 'properties.toZoom',
821
+ 'properties.sortKey',
774
822
  ]
775
823
 
776
- builder = new MutatingForm(this, optionsFields, {
824
+ builder = new MutatingForm(this, advancedFields, {
777
825
  id: 'datalayer-advanced-properties',
778
826
  })
779
827
  builder.on('set', ({ detail }) => {
780
- if (detail.helper.field === 'options.sortKey') {
828
+ if (detail.helper.field === 'properties.sortKey') {
781
829
  this.reindex()
782
830
  }
783
831
  })
784
- const advancedProperties = DomUtil.createFieldset(
832
+ const advancedFieldset = DomUtil.createFieldset(
785
833
  container,
786
834
  translate('Advanced properties')
787
835
  )
788
- advancedProperties.appendChild(builder.build())
836
+ advancedFieldset.appendChild(builder.build())
789
837
 
790
838
  const popupFields = [
791
- 'options.popupShape',
792
- 'options.popupTemplate',
793
- 'options.popupContentTemplate',
794
- 'options.showLabel',
795
- 'options.labelDirection',
796
- 'options.labelInteractive',
797
- 'options.outlinkTarget',
798
- 'options.interactive',
839
+ 'properties.popupShape',
840
+ 'properties.popupTemplate',
841
+ 'properties.popupContentTemplate',
842
+ 'properties.showLabel',
843
+ 'properties.labelDirection',
844
+ 'properties.labelInteractive',
845
+ 'properties.outlinkTarget',
846
+ 'properties.interactive',
799
847
  ]
800
848
  builder = new MutatingForm(this, popupFields)
801
849
  const popupFieldset = DomUtil.createFieldset(
@@ -804,25 +852,38 @@ export class DataLayer {
804
852
  )
805
853
  popupFieldset.appendChild(builder.build())
806
854
 
855
+ const textPathFields = [
856
+ 'properties.textPath',
857
+ 'properties.textPathColor',
858
+ 'properties.textPathRepeat',
859
+ 'properties.textPathRotate',
860
+ 'properties.textPathSize',
861
+ 'properties.textPathOffset',
862
+ 'properties.textPathPosition',
863
+ ]
864
+ builder = new MutatingForm(this, textPathFields)
865
+ const fieldset = DomUtil.createFieldset(container, translate('Line decoration'))
866
+ fieldset.appendChild(builder.build())
867
+
807
868
  // XXX I'm not sure **why** this is needed (as it's set during `this.initialize`)
808
869
  // but apparently it's needed.
809
- if (!Utils.isObject(this.options.remoteData)) {
810
- this.options.remoteData = {}
870
+ if (!Utils.isObject(this.properties.remoteData)) {
871
+ this.properties.remoteData = {}
811
872
  }
812
873
 
813
874
  const remoteDataFields = [
814
875
  [
815
- 'options.remoteData.url',
876
+ 'properties.remoteData.url',
816
877
  { handler: 'Url', label: translate('Url'), helpEntries: ['formatURL'] },
817
878
  ],
818
879
  [
819
- 'options.remoteData.format',
880
+ 'properties.remoteData.format',
820
881
  { handler: 'DataFormat', label: translate('Format') },
821
882
  ],
822
- 'options.fromZoom',
823
- 'options.toZoom',
883
+ 'properties.fromZoom',
884
+ 'properties.toZoom',
824
885
  [
825
- 'options.remoteData.dynamic',
886
+ 'properties.remoteData.dynamic',
826
887
  {
827
888
  handler: 'Switch',
828
889
  label: translate('Dynamic'),
@@ -830,7 +891,7 @@ export class DataLayer {
830
891
  },
831
892
  ],
832
893
  [
833
- 'options.remoteData.licence',
894
+ 'properties.remoteData.licence',
834
895
  {
835
896
  label: translate('Licence'),
836
897
  helpText: translate('Please be sure the licence is compliant with your use.'),
@@ -839,14 +900,14 @@ export class DataLayer {
839
900
  ]
840
901
  if (this._umap.properties.urls.ajax_proxy) {
841
902
  remoteDataFields.push([
842
- 'options.remoteData.proxy',
903
+ 'properties.remoteData.proxy',
843
904
  {
844
905
  handler: 'Switch',
845
906
  label: translate('Proxy request'),
846
907
  helpEntries: ['proxyRemoteData'],
847
908
  },
848
909
  ])
849
- remoteDataFields.push('options.remoteData.ttl')
910
+ remoteDataFields.push('properties.remoteData.ttl')
850
911
  }
851
912
 
852
913
  const remoteDataContainer = DomUtil.createFieldset(
@@ -862,6 +923,7 @@ export class DataLayer {
862
923
  () => this.fetchRemoteData(true),
863
924
  this
864
925
  )
926
+ this.rules.edit(container)
865
927
 
866
928
  if (this._umap.properties.urls.datalayer_versions) {
867
929
  this.buildVersionsFieldset(container)
@@ -871,7 +933,7 @@ export class DataLayer {
871
933
  container,
872
934
  translate('Advanced actions')
873
935
  )
874
- const filename = `${Utils.slugify(this.options.name)}.geojson`
936
+ const filename = `${Utils.slugify(this.properties.name)}.geojson`
875
937
  const tpl = `
876
938
  <div class="button-bar half">
877
939
  <button class="button" type="button" data-ref=del>
@@ -918,22 +980,31 @@ export class DataLayer {
918
980
  })
919
981
  }
920
982
 
921
- getOwnOption(option) {
922
- if (Utils.usableOption(this.options, option)) return this.options[option]
983
+ getOwnProperty(option) {
984
+ if (Utils.usableOption(this.properties, option)) return this.properties[option]
923
985
  }
924
986
 
925
- getOption(option, feature) {
987
+ getProperty(key, feature) {
926
988
  if (this.layer?.getOption) {
927
- const value = this.layer.getOption(option, feature)
989
+ const value = this.layer.getOption(key, feature)
928
990
  if (value !== undefined) return value
929
991
  }
930
- if (this.getOwnOption(option) !== undefined) {
931
- return this.getOwnOption(option)
992
+ if (feature) {
993
+ const value = this.rules.getOption(key, feature)
994
+ if (value !== undefined) return value
995
+ }
996
+ if (this.getOwnProperty(key) !== undefined) {
997
+ return this.getOwnProperty(key)
932
998
  }
933
- if (this.layer?.defaults?.[option]) {
934
- return this.layer.defaults[option]
999
+ if (this.layer?.defaults?.[key]) {
1000
+ return this.layer.defaults[key]
935
1001
  }
936
- return this._umap.getProperty(option, feature)
1002
+ return this._umap.getProperty(key, feature)
1003
+ }
1004
+
1005
+ getOption(key, feature) {
1006
+ // TODO: remove when field.js does not call blindly obj.getOption anymore
1007
+ return this.getProperty(key, feature)
937
1008
  }
938
1009
 
939
1010
  async buildVersionsFieldset(container) {
@@ -973,9 +1044,9 @@ export class DataLayer {
973
1044
  if (!error) {
974
1045
  if (geojson._storage) geojson._umap_options = geojson._storage // Retrocompat.
975
1046
  if (geojson._umap_options) {
976
- const oldOptions = Utils.CopyJSON(this.options)
977
- this.setOptions(geojson._umap_options)
978
- this.sync.update('options', this.options, oldOptions)
1047
+ const oldProperties = Utils.CopyJSON(this.properties)
1048
+ this.setProperties(geojson._umap_options)
1049
+ this.sync.update('properties', this.properties, oldProperties)
979
1050
  }
980
1051
  this.empty()
981
1052
  if (this.isRemoteLayer()) {
@@ -1028,7 +1099,7 @@ export class DataLayer {
1028
1099
 
1029
1100
  zoomToBounds(bounds) {
1030
1101
  if (bounds.isValid()) {
1031
- const options = { maxZoom: this.getOption('zoomTo') }
1102
+ const options = { maxZoom: this.getProperty('zoomTo') }
1032
1103
  this._leafletMap.fitBounds(bounds, options)
1033
1104
  }
1034
1105
  }
@@ -1041,7 +1112,7 @@ export class DataLayer {
1041
1112
  // Is this layer browsable in theorie
1042
1113
  // AND the user allows it
1043
1114
  allowBrowse() {
1044
- return !!this.options.browsable && this.isBrowsable()
1115
+ return !!this.properties.browsable && this.isBrowsable()
1045
1116
  }
1046
1117
 
1047
1118
  // Is this layer browsable in theorie
@@ -1106,7 +1177,7 @@ export class DataLayer {
1106
1177
  return {
1107
1178
  type: 'FeatureCollection',
1108
1179
  features: this.isRemoteLayer() ? [] : this.featuresToGeoJSON(),
1109
- _umap_options: this.options,
1180
+ _umap_options: this.properties,
1110
1181
  }
1111
1182
  }
1112
1183
 
@@ -1116,7 +1187,7 @@ export class DataLayer {
1116
1187
 
1117
1188
  isReadOnly() {
1118
1189
  // isReadOnly must return true if unset
1119
- return this.options.editMode === 'disabled'
1190
+ return this.properties.editMode === 'disabled'
1120
1191
  }
1121
1192
 
1122
1193
  isDataReadOnly() {
@@ -1133,10 +1204,10 @@ export class DataLayer {
1133
1204
  }
1134
1205
  }
1135
1206
 
1136
- prepareOptions() {
1137
- const options = Utils.CopyJSON(this.options)
1138
- delete options.permissions
1139
- return JSON.stringify(options)
1207
+ prepareProperties() {
1208
+ const properties = Utils.CopyJSON(this.properties)
1209
+ delete properties.permissions
1210
+ return JSON.stringify(properties)
1140
1211
  }
1141
1212
 
1142
1213
  async save() {
@@ -1144,10 +1215,10 @@ export class DataLayer {
1144
1215
  if (!this.isRemoteLayer() && !this.isLoaded()) return
1145
1216
  const geojson = this.umapGeoJSON()
1146
1217
  const formData = new FormData()
1147
- formData.append('name', this.options.name)
1148
- formData.append('display_on_load', !!this.options.displayOnLoad)
1218
+ formData.append('name', this.properties.name)
1219
+ formData.append('display_on_load', !!this.properties.displayOnLoad)
1149
1220
  formData.append('rank', this.rank)
1150
- formData.append('settings', this.prepareOptions())
1221
+ formData.append('settings', this.prepareProperties())
1151
1222
  // Filename support is shaky, don't do it for now.
1152
1223
  const blob = new Blob([JSON.stringify(geojson)], { type: 'application/json' })
1153
1224
  formData.append('geojson', blob)
@@ -1196,11 +1267,10 @@ export class DataLayer {
1196
1267
  }
1197
1268
  delete data.id
1198
1269
  delete data._referenceVersion
1199
- this.updateOptions(data)
1270
+ this.updateProperties(data)
1200
1271
 
1201
1272
  this.setReferenceVersion({ response, sync: true })
1202
1273
 
1203
- this.backupOptions()
1204
1274
  this.backupData()
1205
1275
  this.connectToMap()
1206
1276
  this.redraw() // Needed for reordering features
@@ -1221,7 +1291,7 @@ export class DataLayer {
1221
1291
  }
1222
1292
 
1223
1293
  getName() {
1224
- return this.options.name || translate('Untitled layer')
1294
+ return this.properties.name || translate('Untitled layer')
1225
1295
  }
1226
1296
 
1227
1297
  getPermalink() {
@@ -1241,7 +1311,7 @@ export class DataLayer {
1241
1311
  // By default, it will we use the "name" property, which is also the one used as label in the features list.
1242
1312
  // When map owner has configured another label or sort key, we try to be smart and search in the same keys.
1243
1313
  if (this._umap.properties.filterKey) return this._umap.properties.filterKey
1244
- if (this.getOption('labelKey')) return this.getOption('labelKey')
1314
+ if (this.getProperty('labelKey')) return this.getProperty('labelKey')
1245
1315
  if (this._umap.properties.sortKey) return this._umap.properties.sortKey
1246
1316
  return 'displayName'
1247
1317
  }