umap-project 3.1.2__py3-none-any.whl → 3.3.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 (199) hide show
  1. umap/__init__.py +1 -1
  2. umap/locale/en/LC_MESSAGES/django.mo +0 -0
  3. umap/locale/en/LC_MESSAGES/django.po +22 -18
  4. umap/locale/fr/LC_MESSAGES/django.mo +0 -0
  5. umap/locale/fr/LC_MESSAGES/django.po +21 -17
  6. umap/management/commands/export_pictogram.py +29 -0
  7. umap/management/commands/migrate_to_S3.py +5 -1
  8. umap/management/commands/purge_old_versions.py +8 -6
  9. umap/settings/__init__.py +21 -0
  10. umap/settings/base.py +3 -0
  11. umap/static/umap/content.css +7 -2
  12. umap/static/umap/css/contextmenu.css +58 -2
  13. umap/static/umap/css/form.css +175 -45
  14. umap/static/umap/css/icon.css +97 -3
  15. umap/static/umap/css/panel.css +31 -1
  16. umap/static/umap/img/16-white.svg +21 -40
  17. umap/static/umap/img/16.svg +1 -1
  18. umap/static/umap/img/24-white.svg +9 -9
  19. umap/static/umap/img/24.svg +23 -10
  20. umap/static/umap/img/source/16-white.svg +23 -41
  21. umap/static/umap/img/source/16.svg +1 -1
  22. umap/static/umap/img/source/24-white.svg +11 -11
  23. umap/static/umap/img/source/24.svg +25 -12
  24. umap/static/umap/js/modules/browser.js +1 -1
  25. umap/static/umap/js/modules/caption.js +8 -0
  26. umap/static/umap/js/modules/data/features.js +331 -202
  27. umap/static/umap/js/modules/data/layer.js +263 -152
  28. umap/static/umap/js/modules/facets.js +2 -2
  29. umap/static/umap/js/modules/form/builder.js +11 -7
  30. umap/static/umap/js/modules/form/fields.js +66 -26
  31. umap/static/umap/js/modules/formatter.js +78 -28
  32. umap/static/umap/js/modules/importer.js +6 -1
  33. umap/static/umap/js/modules/importers/opendata.js +138 -33
  34. umap/static/umap/js/modules/importers/openrouteservice.js +140 -0
  35. umap/static/umap/js/modules/managers.js +67 -0
  36. umap/static/umap/js/modules/printer.js +107 -0
  37. umap/static/umap/js/modules/rendering/controls.js +78 -2
  38. umap/static/umap/js/modules/rendering/icon.js +116 -87
  39. umap/static/umap/js/modules/rendering/layers/classified.js +8 -7
  40. umap/static/umap/js/modules/rendering/layers/cluster.js +199 -63
  41. umap/static/umap/js/modules/rendering/map.js +6 -2
  42. umap/static/umap/js/modules/rendering/template.js +71 -1
  43. umap/static/umap/js/modules/rendering/ui.js +111 -34
  44. umap/static/umap/js/modules/rules.js +76 -23
  45. umap/static/umap/js/modules/schema.js +27 -0
  46. umap/static/umap/js/modules/share.js +19 -12
  47. umap/static/umap/js/modules/slideshow.js +1 -1
  48. umap/static/umap/js/modules/sync/updaters.js +1 -6
  49. umap/static/umap/js/modules/tableeditor.js +13 -37
  50. umap/static/umap/js/modules/templates.js +7 -6
  51. umap/static/umap/js/modules/ui/bar.js +6 -1
  52. umap/static/umap/js/modules/ui/base.js +24 -9
  53. umap/static/umap/js/modules/ui/contextmenu.js +17 -7
  54. umap/static/umap/js/modules/ui/dialog.js +7 -4
  55. umap/static/umap/js/modules/ui/panel.js +7 -0
  56. umap/static/umap/js/modules/umap.js +84 -67
  57. umap/static/umap/js/modules/utils.js +8 -7
  58. umap/static/umap/js/umap.controls.js +22 -57
  59. umap/static/umap/locale/am_ET.js +81 -9
  60. umap/static/umap/locale/am_ET.json +81 -9
  61. umap/static/umap/locale/ar.js +81 -9
  62. umap/static/umap/locale/ar.json +81 -9
  63. umap/static/umap/locale/ast.js +81 -9
  64. umap/static/umap/locale/ast.json +81 -9
  65. umap/static/umap/locale/bg.js +81 -9
  66. umap/static/umap/locale/bg.json +81 -9
  67. umap/static/umap/locale/br.js +68 -29
  68. umap/static/umap/locale/br.json +68 -29
  69. umap/static/umap/locale/ca.js +88 -16
  70. umap/static/umap/locale/ca.json +88 -16
  71. umap/static/umap/locale/cs_CZ.js +81 -9
  72. umap/static/umap/locale/cs_CZ.json +81 -9
  73. umap/static/umap/locale/da.js +48 -9
  74. umap/static/umap/locale/da.json +48 -9
  75. umap/static/umap/locale/de.js +48 -9
  76. umap/static/umap/locale/de.json +48 -9
  77. umap/static/umap/locale/el.js +58 -13
  78. umap/static/umap/locale/el.json +58 -13
  79. umap/static/umap/locale/en.js +48 -9
  80. umap/static/umap/locale/en.json +48 -9
  81. umap/static/umap/locale/en_US.json +81 -9
  82. umap/static/umap/locale/es.js +48 -9
  83. umap/static/umap/locale/es.json +48 -9
  84. umap/static/umap/locale/et.js +81 -9
  85. umap/static/umap/locale/et.json +81 -9
  86. umap/static/umap/locale/eu.js +97 -25
  87. umap/static/umap/locale/eu.json +97 -25
  88. umap/static/umap/locale/fa_IR.js +81 -9
  89. umap/static/umap/locale/fa_IR.json +81 -9
  90. umap/static/umap/locale/fi.js +81 -9
  91. umap/static/umap/locale/fi.json +81 -9
  92. umap/static/umap/locale/fr.js +48 -9
  93. umap/static/umap/locale/fr.json +48 -9
  94. umap/static/umap/locale/gl.js +81 -9
  95. umap/static/umap/locale/gl.json +81 -9
  96. umap/static/umap/locale/he.js +81 -9
  97. umap/static/umap/locale/he.json +81 -9
  98. umap/static/umap/locale/hr.js +81 -9
  99. umap/static/umap/locale/hr.json +81 -9
  100. umap/static/umap/locale/hu.js +72 -27
  101. umap/static/umap/locale/hu.json +72 -27
  102. umap/static/umap/locale/id.js +81 -9
  103. umap/static/umap/locale/id.json +81 -9
  104. umap/static/umap/locale/is.js +81 -9
  105. umap/static/umap/locale/is.json +81 -9
  106. umap/static/umap/locale/it.js +48 -9
  107. umap/static/umap/locale/it.json +48 -9
  108. umap/static/umap/locale/ja.js +81 -9
  109. umap/static/umap/locale/ja.json +81 -9
  110. umap/static/umap/locale/ko.js +81 -9
  111. umap/static/umap/locale/ko.json +81 -9
  112. umap/static/umap/locale/lt.js +81 -9
  113. umap/static/umap/locale/lt.json +81 -9
  114. umap/static/umap/locale/ms.js +81 -9
  115. umap/static/umap/locale/ms.json +81 -9
  116. umap/static/umap/locale/nl.js +48 -9
  117. umap/static/umap/locale/nl.json +48 -9
  118. umap/static/umap/locale/no.js +81 -9
  119. umap/static/umap/locale/no.json +81 -9
  120. umap/static/umap/locale/pl.js +81 -9
  121. umap/static/umap/locale/pl.json +81 -9
  122. umap/static/umap/locale/pl_PL.json +81 -9
  123. umap/static/umap/locale/pt.js +81 -9
  124. umap/static/umap/locale/pt.json +81 -9
  125. umap/static/umap/locale/pt_BR.js +91 -19
  126. umap/static/umap/locale/pt_BR.json +91 -19
  127. umap/static/umap/locale/pt_PT.js +81 -9
  128. umap/static/umap/locale/pt_PT.json +81 -9
  129. umap/static/umap/locale/ro.js +81 -9
  130. umap/static/umap/locale/ro.json +81 -9
  131. umap/static/umap/locale/ru.js +81 -9
  132. umap/static/umap/locale/ru.json +81 -9
  133. umap/static/umap/locale/sk_SK.js +81 -9
  134. umap/static/umap/locale/sk_SK.json +81 -9
  135. umap/static/umap/locale/sl.js +81 -9
  136. umap/static/umap/locale/sl.json +81 -9
  137. umap/static/umap/locale/sr.js +81 -9
  138. umap/static/umap/locale/sr.json +81 -9
  139. umap/static/umap/locale/sv.js +81 -9
  140. umap/static/umap/locale/sv.json +81 -9
  141. umap/static/umap/locale/th_TH.js +81 -9
  142. umap/static/umap/locale/th_TH.json +81 -9
  143. umap/static/umap/locale/tr.js +81 -9
  144. umap/static/umap/locale/tr.json +81 -9
  145. umap/static/umap/locale/uk_UA.js +81 -9
  146. umap/static/umap/locale/uk_UA.json +81 -9
  147. umap/static/umap/locale/vi.js +81 -9
  148. umap/static/umap/locale/vi.json +81 -9
  149. umap/static/umap/locale/vi_VN.json +81 -9
  150. umap/static/umap/locale/zh.js +81 -9
  151. umap/static/umap/locale/zh.json +81 -9
  152. umap/static/umap/locale/zh_CN.json +81 -9
  153. umap/static/umap/locale/zh_TW.Big5.json +81 -9
  154. umap/static/umap/locale/zh_TW.js +98 -26
  155. umap/static/umap/locale/zh_TW.json +98 -26
  156. umap/static/umap/map.css +325 -102
  157. umap/static/umap/vars.css +1 -0
  158. umap/static/umap/vendors/betterknown/betterknown.mjs +287 -0
  159. umap/static/umap/vendors/editable/Leaflet.Editable.js +3 -1
  160. umap/static/umap/vendors/openrouteservice/ors-js-client.js +521 -0
  161. umap/static/umap/vendors/openrouteservice/ors-js-client.js.map +1 -0
  162. umap/static/umap/vendors/simple-elevation-chart/elevation.js +63 -0
  163. umap/static/umap/vendors/simple-elevation-chart/elevation.svg +8 -0
  164. umap/static/umap/vendors/snapdom/snapdom.min.mjs +3 -0
  165. umap/storage/fs.py +3 -2
  166. umap/storage/staticfiles.py +12 -0
  167. umap/templates/base.html +4 -1
  168. umap/templates/umap/css.html +0 -4
  169. umap/templates/umap/js.html +1 -3
  170. umap/tests/base.py +9 -1
  171. umap/tests/integration/test_basics.py +3 -1
  172. umap/tests/integration/test_conditional_rules.py +79 -37
  173. umap/tests/integration/test_datalayer.py +1 -1
  174. umap/tests/integration/test_draw_polygon.py +3 -5
  175. umap/tests/integration/test_draw_polyline.py +4 -6
  176. umap/tests/integration/test_draw_route.py +178 -0
  177. umap/tests/integration/test_edit_datalayer.py +1 -1
  178. umap/tests/integration/test_edit_map.py +1 -1
  179. umap/tests/integration/test_edit_marker.py +8 -8
  180. umap/tests/integration/test_edit_polygon.py +2 -2
  181. umap/tests/integration/test_export_map.py +84 -10
  182. umap/tests/integration/test_import.py +140 -0
  183. umap/tests/integration/test_map_preview.py +1 -1
  184. umap/tests/integration/test_optimistic_merge.py +72 -12
  185. umap/tests/integration/test_share.py +1 -1
  186. umap/tests/integration/test_tableeditor.py +10 -7
  187. umap/tests/integration/test_websocket_sync.py +4 -4
  188. umap/utils.py +37 -0
  189. umap/views.py +18 -2
  190. umap_project-3.3.0.dist-info/METADATA +76 -0
  191. {umap_project-3.1.2.dist-info → umap_project-3.3.0.dist-info}/RECORD +194 -188
  192. umap/static/umap/vendors/markercluster/MarkerCluster.Default.css +0 -60
  193. umap/static/umap/vendors/markercluster/MarkerCluster.css +0 -14
  194. umap/static/umap/vendors/markercluster/leaflet.markercluster.js +0 -2
  195. umap/static/umap/vendors/markercluster/leaflet.markercluster.js.map +0 -1
  196. umap_project-3.1.2.dist-info/METADATA +0 -68
  197. {umap_project-3.1.2.dist-info → umap_project-3.3.0.dist-info}/WHEEL +0 -0
  198. {umap_project-3.1.2.dist-info → umap_project-3.3.0.dist-info}/entry_points.txt +0 -0
  199. {umap_project-3.1.2.dist-info → umap_project-3.3.0.dist-info}/licenses/LICENSE +0 -0
@@ -21,6 +21,8 @@ import TableEditor from '../tableeditor.js'
21
21
  import * as Utils from '../utils.js'
22
22
  import { LineString, Point, Polygon } from './features.js'
23
23
  import Rules from '../rules.js'
24
+ import Orderable from '../orderable.js'
25
+ import { FeatureManager } from '../managers.js'
24
26
 
25
27
  export const LAYER_TYPES = [
26
28
  DefaultLayer,
@@ -40,8 +42,7 @@ export class DataLayer {
40
42
  constructor(umap, leafletMap, data = {}) {
41
43
  this._umap = umap
42
44
  this.sync = umap.syncEngine.proxy(this)
43
- this._index = Array()
44
- this._features = {}
45
+ this.features = new FeatureManager()
45
46
  this._geojson = null
46
47
  this._propertiesIndex = []
47
48
 
@@ -89,6 +90,12 @@ export class DataLayer {
89
90
  if (!this.createdOnServer) {
90
91
  if (this.showAtLoad()) this.show()
91
92
  }
93
+ if (!this._needsFetch && !this._umap.fields.length) {
94
+ this.properties.fields = [
95
+ { key: U.DEFAULT_LABEL_KEY, type: 'String' },
96
+ { key: 'description', type: 'Text' },
97
+ ]
98
+ }
92
99
 
93
100
  // Only layers that are displayed on load must be hidden/shown
94
101
  // Automatically, others will be shown manually, and thus will
@@ -140,6 +147,23 @@ export class DataLayer {
140
147
  this.properties.rank = value
141
148
  }
142
149
 
150
+ get fields() {
151
+ if (!this.properties.fields) this.properties.fields = []
152
+ return this.properties.fields
153
+ }
154
+
155
+ set fields(fields) {
156
+ this.properties.fields = fields
157
+ }
158
+
159
+ get fieldKeys() {
160
+ return this.fields.map((field) => field.key)
161
+ }
162
+
163
+ get sortKey() {
164
+ return this.getProperty('sortKey') || U.DEFAULT_LABEL_KEY
165
+ }
166
+
143
167
  getSyncMetadata() {
144
168
  return {
145
169
  subject: 'datalayer',
@@ -249,19 +273,14 @@ export class DataLayer {
249
273
  const Class = LAYER_MAP[this.properties.type] || DefaultLayer
250
274
  this.layer = new Class(this)
251
275
  // Rendering layer changed, so let's force reset the feature rendering too.
252
- this.eachFeature((feature) => feature.makeUI())
253
- this.eachFeature(this.showFeature)
276
+ this.features.forEach((feature) => {
277
+ feature.makeUI()
278
+ this.showFeature(feature)
279
+ })
254
280
  if (visible) this.show()
255
281
  this.propagateRemote()
256
282
  }
257
283
 
258
- eachFeature(method, context) {
259
- for (const idx of this._index) {
260
- method.call(context || this, this._features[idx])
261
- }
262
- return this
263
- }
264
-
265
284
  async fetchData() {
266
285
  if (!this.createdOnServer) return
267
286
  if (this._loading) return
@@ -279,7 +298,7 @@ export class DataLayer {
279
298
  }
280
299
 
281
300
  dataChanged() {
282
- if (!this.isLoaded()) return
301
+ if (!this.isLoaded() || this._batch) return
283
302
  this._umap.onDataLayersChanged()
284
303
  this.layer.dataChanged()
285
304
  }
@@ -314,12 +333,6 @@ export class DataLayer {
314
333
  }
315
334
  }
316
335
 
317
- reindex() {
318
- const features = Object.values(this._features)
319
- this.sortFeatures(features)
320
- this._index = features.map((feature) => stamp(feature))
321
- }
322
-
323
336
  showAtZoom() {
324
337
  const from = Number.parseInt(this.properties.fromZoom, 10)
325
338
  const to = Number.parseInt(this.properties.toZoom, 10)
@@ -423,22 +436,20 @@ export class DataLayer {
423
436
  }
424
437
 
425
438
  addFeature(feature) {
426
- const id = stamp(feature)
427
439
  feature.connectToDataLayer(this)
428
- this._index.push(id)
429
- this._features[id] = feature
430
- this.indexProperties(feature)
440
+ this.features.add(feature)
431
441
  this._umap.featuresIndex[feature.getSlug()] = feature
442
+ // TODO: quid for remote data ?
443
+ this.inferFields(feature)
432
444
  this.showFeature(feature)
433
445
  this.dataChanged()
434
446
  }
435
447
 
436
448
  removeFeature(feature, sync) {
437
- const id = stamp(feature)
438
449
  // This feature was not yet added, may be after
439
450
  // hitting Escape while drawing a new line or
440
451
  // polygon, not yet valid (not enough points)
441
- if (!this._index.includes(id)) return
452
+ if (!this.features.has(feature.id)) return
442
453
  if (sync !== false) {
443
454
  const oldValue = feature.toGeoJSON()
444
455
  feature.sync.delete(oldValue)
@@ -446,57 +457,66 @@ export class DataLayer {
446
457
  this.hideFeature(feature)
447
458
  delete this._umap.featuresIndex[feature.getSlug()]
448
459
  feature.disconnectFromDataLayer(this)
449
- this._index.splice(this._index.indexOf(id), 1)
450
- delete this._features[id]
460
+ this.features.del(feature)
451
461
  if (this.isVisible()) this.dataChanged()
452
462
  }
453
463
 
454
- indexProperties(feature) {
455
- for (const i in feature.properties)
456
- if (typeof feature.properties[i] !== 'object') this.indexProperty(i)
457
- }
458
-
459
- indexProperty(name) {
460
- if (!name) return
461
- if (name.indexOf('_') === 0) return
462
- if (!this._propertiesIndex.includes(name)) {
463
- this._propertiesIndex.push(name)
464
- this._propertiesIndex.sort()
464
+ inferFields(feature) {
465
+ if (!this.properties.fields) this.properties.fields = []
466
+ const keys = this.fieldKeys
467
+ for (const key in feature.properties) {
468
+ if (typeof feature.properties[key] !== 'object') {
469
+ if (key.indexOf('_') === 0) continue
470
+ if (keys.includes(key)) continue
471
+ this.properties.fields.push({ key, type: 'String' })
472
+ }
465
473
  }
466
474
  }
467
475
 
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
+ async confirmDeleteProperty(property) {
477
+ return this._umap.dialog
478
+ .confirm(
479
+ translate('Are you sure you want to delete this field on all the features?')
480
+ )
481
+ .then(() => {
482
+ this.deleteProperty(property)
483
+ })
476
484
  }
477
485
 
478
- deindexProperty(name) {
479
- const idx = this._propertiesIndex.indexOf(name)
480
- if (idx !== -1) this._propertiesIndex.splice(idx, 1)
486
+ async askForRenameProperty(property) {
487
+ return this._umap.dialog
488
+ .prompt(translate('Please enter the new name of this field'))
489
+ .then(({ prompt }) => {
490
+ if (!prompt || !this.validateName(prompt)) return
491
+ this.renameProperty(property, prompt)
492
+ })
481
493
  }
482
494
 
483
495
  renameProperty(oldName, newName) {
484
496
  this.sync.startBatch()
485
- this.eachFeature((feature) => {
497
+ const oldFields = Utils.CopyJSON(this.fields)
498
+ for (const field of this.fields) {
499
+ if (field.key === oldName) {
500
+ field.key = newName
501
+ break
502
+ }
503
+ }
504
+ this.sync.update('properties.fields', this.fields, oldFields)
505
+ this.features.forEach((feature) => {
486
506
  feature.renameProperty(oldName, newName)
487
507
  })
488
508
  this.sync.commitBatch()
489
- this.deindexProperty(oldName)
490
- this.indexProperty(newName)
491
509
  }
492
510
 
493
511
  deleteProperty(property) {
494
512
  this.sync.startBatch()
495
- this.eachFeature((feature) => {
513
+ const oldFields = Utils.CopyJSON(this.fields)
514
+ this.fields = this.fields.filter((field) => field.key !== property)
515
+ this.sync.update('properties.fields', this.fields, oldFields)
516
+ this.features.forEach((feature) => {
496
517
  feature.deleteProperty(property)
497
518
  })
498
519
  this.sync.commitBatch()
499
- this.deindexProperty(property)
500
520
  }
501
521
 
502
522
  addProperty() {
@@ -508,7 +528,7 @@ export class DataLayer {
508
528
  .prompt(translate('Please enter the name of the property'))
509
529
  .then(({ prompt }) => {
510
530
  if (!prompt || !this.validateName(prompt)) return
511
- this.indexProperty(prompt)
531
+ this.properties.fields.push({ key: prompt, type: 'String' })
512
532
  resolve()
513
533
  })
514
534
  return promise
@@ -519,39 +539,35 @@ export class DataLayer {
519
539
  Alert.error(translate('Name “{name}” should not contain a dot.', { name }))
520
540
  return false
521
541
  }
522
- if (this.allProperties().includes(name)) {
542
+ if (this.fieldKeys.includes(name)) {
523
543
  Alert.error(translate('This name already exists: “{name}”', { name }))
524
544
  return false
525
545
  }
526
546
  return true
527
547
  }
528
548
 
529
- allProperties() {
530
- return this._propertiesIndex
531
- }
532
-
533
549
  sortedValues(property) {
534
- return Object.values(this._features)
550
+ return this.features
551
+ .all()
535
552
  .map((feature) => feature.properties[property])
536
553
  .filter((val, idx, arr) => arr.indexOf(val) === idx)
537
554
  .sort(Utils.naturalSort)
538
555
  }
539
556
 
540
557
  addData(geojson, sync) {
558
+ let data = []
559
+ this._batch = true
541
560
  try {
542
561
  // Do not fail if remote data is somehow invalid,
543
562
  // otherwise the layer becomes uneditable.
544
- return this.makeFeatures(geojson, sync)
563
+ data = this.makeFeatures(geojson, sync)
545
564
  } catch (err) {
546
565
  console.debug('Error with DataLayer', this.id)
547
566
  console.error(err)
548
- return []
549
567
  }
550
- }
551
-
552
- sortFeatures(collection) {
553
- const sortKeys = this.getProperty('sortKey') || U.DEFAULT_LABEL_KEY
554
- return Utils.sortFeatures(collection, sortKeys, U.lang)
568
+ this._batch = false
569
+ this.dataChanged()
570
+ return data
555
571
  }
556
572
 
557
573
  makeFeatures(geojson = {}, sync = true) {
@@ -563,7 +579,7 @@ export class DataLayer {
563
579
  : geojson.features || geojson.geometries
564
580
  if (!collection) return
565
581
  const features = []
566
- this.sortFeatures(collection)
582
+ Utils.sortFeatures(collection, this.sortKey, U.lang)
567
583
  for (const featureJson of collection) {
568
584
  if (featureJson.geometry?.type === 'GeometryCollection') {
569
585
  for (const geometry of featureJson.geometry.geometries) {
@@ -592,6 +608,14 @@ export class DataLayer {
592
608
  // FIXME: deal with MultiPoint
593
609
  feature = new Point(this._umap, this, geojson, id)
594
610
  break
611
+ case 'MultiPoint':
612
+ if (geometry.coordinates?.length === 1) {
613
+ geojson.geometry.coordinates = geojson.geometry.coordinates[0]
614
+ feature = new Point(this._umap, this, geojson, id)
615
+ } else if (this._umap.editEnabled) {
616
+ Alert.error(translate('Cannot process MultiPoint'))
617
+ }
618
+ break
595
619
  case 'MultiLineString':
596
620
  case 'LineString':
597
621
  feature = new LineString(this._umap, this, geojson, id)
@@ -602,11 +626,13 @@ export class DataLayer {
602
626
  break
603
627
  default:
604
628
  console.debug(geojson)
605
- Alert.error(
606
- translate('Skipping unknown geometry.type: {type}', {
607
- type: geometry.type || 'undefined',
608
- })
609
- )
629
+ if (this._umap.editEnabled) {
630
+ Alert.error(
631
+ translate('Skipping unknown geometry.type: {type}', {
632
+ type: geometry.type || 'undefined',
633
+ })
634
+ )
635
+ }
610
636
  }
611
637
  if (feature && !feature.isEmpty()) {
612
638
  this.addFeature(feature)
@@ -713,9 +739,7 @@ export class DataLayer {
713
739
  }
714
740
 
715
741
  clear(sync = true) {
716
- for (const feature of Object.values(this._features)) {
717
- feature.del(sync)
718
- }
742
+ this.features.forEach((feature) => feature.del(sync))
719
743
  this.dataChanged()
720
744
  }
721
745
 
@@ -731,14 +755,17 @@ export class DataLayer {
731
755
 
732
756
  redraw() {
733
757
  if (!this.isVisible()) return
734
- this.eachFeature((feature) => feature.redraw())
758
+ this.features.forEach((feature) => feature.redraw())
735
759
  }
736
760
 
737
- edit() {
738
- if (!this._umap.editEnabled) {
739
- return
761
+ reindex() {
762
+ this.features.sort(this.sortKey)
763
+ if (this.isBrowsable()) {
764
+ this.resetLayer(true)
740
765
  }
741
- const container = DomUtil.create('div', 'umap-layer-properties-container')
766
+ }
767
+
768
+ _editMetadata(container) {
742
769
  const metadataFields = [
743
770
  'properties.name',
744
771
  'properties.description',
@@ -768,31 +795,38 @@ export class DataLayer {
768
795
  ],
769
796
  ]
770
797
  DomUtil.createTitle(container, translate('Layer properties'), 'icon-layers')
771
- let builder = new MutatingForm(this, metadataFields)
798
+ const builder = new MutatingForm(this, metadataFields)
772
799
  builder.on('set', ({ detail }) => {
773
800
  this._umap.onDataLayersChanged()
774
801
  if (detail.helper.field === 'properties.type') {
775
- this.edit()
802
+ this.edit().then((panel) => panel.scrollTo('details#layer-properties'))
776
803
  }
777
804
  })
778
805
  container.appendChild(builder.build())
806
+ }
779
807
 
808
+ _editLayerProperties(container) {
780
809
  const layerFields = this.layer.getEditableProperties()
781
810
 
782
811
  if (layerFields.length) {
783
- builder = new MutatingForm(this, layerFields, {
784
- id: 'datalayer-layer-properties',
785
- })
786
- const layerProperties = DomUtil.createFieldset(
787
- container,
788
- `${this.layer.getName()}: ${translate('settings')}`
789
- )
790
- layerProperties.appendChild(builder.build())
812
+ const builder = new MutatingForm(this, layerFields)
813
+ const template = `
814
+ <details id="layer-properties">
815
+ <summary>${this.layer.getName()}: ${translate('settings')}</summary>
816
+ <fieldset data-ref=fieldset></fieldset>
817
+ </details>
818
+ `
819
+ const [details, { fieldset }] = Utils.loadTemplateWithRefs(template)
820
+ container.appendChild(details)
821
+ fieldset.appendChild(builder.build())
791
822
  }
823
+ }
792
824
 
793
- const shapeFields = [
825
+ _editShapeProperties(container) {
826
+ const fields = [
794
827
  'properties.color',
795
828
  'properties.iconClass',
829
+ 'properties.iconSize',
796
830
  'properties.iconUrl',
797
831
  'properties.iconOpacity',
798
832
  'properties.opacity',
@@ -803,7 +837,7 @@ export class DataLayer {
803
837
  'properties.fillOpacity',
804
838
  ]
805
839
 
806
- builder = new MutatingForm(this, shapeFields, {
840
+ const builder = new MutatingForm(this, fields, {
807
841
  id: 'datalayer-advanced-properties',
808
842
  })
809
843
  const shapeFieldset = DomUtil.createFieldset(
@@ -811,8 +845,10 @@ export class DataLayer {
811
845
  translate('Shape properties')
812
846
  )
813
847
  shapeFieldset.appendChild(builder.build())
848
+ }
814
849
 
815
- const advancedFields = [
850
+ _editAdvancedProperties(container) {
851
+ const fields = [
816
852
  'properties.smoothFactor',
817
853
  'properties.dashArray',
818
854
  'properties.zoomTo',
@@ -821,7 +857,7 @@ export class DataLayer {
821
857
  'properties.sortKey',
822
858
  ]
823
859
 
824
- builder = new MutatingForm(this, advancedFields, {
860
+ const builder = new MutatingForm(this, fields, {
825
861
  id: 'datalayer-advanced-properties',
826
862
  })
827
863
  builder.on('set', ({ detail }) => {
@@ -834,8 +870,10 @@ export class DataLayer {
834
870
  translate('Advanced properties')
835
871
  )
836
872
  advancedFieldset.appendChild(builder.build())
873
+ }
837
874
 
838
- const popupFields = [
875
+ _editInteractionProperties(container) {
876
+ const fields = [
839
877
  'properties.popupShape',
840
878
  'properties.popupTemplate',
841
879
  'properties.popupContentTemplate',
@@ -845,14 +883,16 @@ export class DataLayer {
845
883
  'properties.outlinkTarget',
846
884
  'properties.interactive',
847
885
  ]
848
- builder = new MutatingForm(this, popupFields)
886
+ const builder = new MutatingForm(this, fields)
849
887
  const popupFieldset = DomUtil.createFieldset(
850
888
  container,
851
889
  translate('Interaction options')
852
890
  )
853
891
  popupFieldset.appendChild(builder.build())
892
+ }
854
893
 
855
- const textPathFields = [
894
+ _editTextPathProperties(container) {
895
+ const fields = [
856
896
  'properties.textPath',
857
897
  'properties.textPathColor',
858
898
  'properties.textPathRepeat',
@@ -861,17 +901,77 @@ export class DataLayer {
861
901
  'properties.textPathOffset',
862
902
  'properties.textPathPosition',
863
903
  ]
864
- builder = new MutatingForm(this, textPathFields)
904
+ const builder = new MutatingForm(this, fields)
865
905
  const fieldset = DomUtil.createFieldset(container, translate('Line decoration'))
866
906
  fieldset.appendChild(builder.build())
907
+ }
867
908
 
909
+ _editFields(container) {
910
+ const template = `
911
+ <details id="fields">
912
+ <summary>${translate('Manage Fields')}</summary>
913
+ <fieldset>
914
+ <ul data-ref=ul></ul>
915
+ <button type="button" data-ref=add><i class="icon icon-16 icon-add"></i>${translate('Add a new field')}</button>
916
+ </fieldset>
917
+ </details>
918
+ `
919
+ const [fieldset, { ul, add }] = Utils.loadTemplateWithRefs(template)
920
+ add.addEventListener('click', () => {
921
+ this.addProperty().then(() => {
922
+ this.edit().then((panel) => {
923
+ panel.scrollTo('details#fields')
924
+ })
925
+ })
926
+ })
927
+ container.appendChild(fieldset)
928
+ for (const field of this.fields) {
929
+ const [row, { rename, del }] = Utils.loadTemplateWithRefs(
930
+ `<li class="orderable" data-key="${field.key}">
931
+ <button class="icon icon-16 icon-edit" title="${translate('Rename this field')}" data-ref=rename></button>
932
+ <button class="icon icon-16 icon-delete" title="${translate('Delete this field')}" data-ref=del></button>
933
+ <i class="icon icon-16 icon-drag" title="${translate('Drag to reorder')}"></i>
934
+ ${field.key}
935
+ </li>`
936
+ )
937
+ ul.appendChild(row)
938
+ rename.addEventListener('click', () => {
939
+ this.askForRenameProperty(field.key).then(() => {
940
+ this.edit().then((panel) => {
941
+ panel.scrollTo('details#fields')
942
+ })
943
+ })
944
+ })
945
+ del.addEventListener('click', () => {
946
+ this.confirmDeleteProperty(field.key).then(() => {
947
+ this.edit().then((panel) => {
948
+ panel.scrollTo('details#fields')
949
+ })
950
+ })
951
+ })
952
+ }
953
+ const onReorder = (src, dst, initialIndex, finalIndex) => {
954
+ const orderedKeys = Array.from(ul.querySelectorAll('li')).map(
955
+ (el) => el.dataset.key
956
+ )
957
+ const oldFields = Utils.CopyJSON(this.properties.fields)
958
+ this.properties.fields.sort(
959
+ (fieldA, fieldB) =>
960
+ orderedKeys.indexOf(fieldA.key) > orderedKeys.indexOf(fieldB.key)
961
+ )
962
+ this.sync.update('properties.fields', this.properties.fields, oldFields)
963
+ }
964
+ const orderable = new Orderable(ul, onReorder)
965
+ }
966
+
967
+ _editRemoteDataProperties(container) {
868
968
  // XXX I'm not sure **why** this is needed (as it's set during `this.initialize`)
869
969
  // but apparently it's needed.
870
970
  if (!Utils.isObject(this.properties.remoteData)) {
871
971
  this.properties.remoteData = {}
872
972
  }
873
973
 
874
- const remoteDataFields = [
974
+ const fields = [
875
975
  [
876
976
  'properties.remoteData.url',
877
977
  { handler: 'Url', label: translate('Url'), helpEntries: ['formatURL'] },
@@ -899,7 +999,7 @@ export class DataLayer {
899
999
  ],
900
1000
  ]
901
1001
  if (this._umap.properties.urls.ajax_proxy) {
902
- remoteDataFields.push([
1002
+ fields.push([
903
1003
  'properties.remoteData.proxy',
904
1004
  {
905
1005
  handler: 'Switch',
@@ -907,14 +1007,14 @@ export class DataLayer {
907
1007
  helpEntries: ['proxyRemoteData'],
908
1008
  },
909
1009
  ])
910
- remoteDataFields.push('properties.remoteData.ttl')
1010
+ fields.push('properties.remoteData.ttl')
911
1011
  }
912
1012
 
913
1013
  const remoteDataContainer = DomUtil.createFieldset(
914
1014
  container,
915
1015
  translate('Remote data')
916
1016
  )
917
- builder = new MutatingForm(this, remoteDataFields)
1017
+ const builder = new MutatingForm(this, fields)
918
1018
  remoteDataContainer.appendChild(builder.build())
919
1019
  DomUtil.createButton(
920
1020
  'button umap-verify',
@@ -923,12 +1023,9 @@ export class DataLayer {
923
1023
  () => this.fetchRemoteData(true),
924
1024
  this
925
1025
  )
926
- this.rules.edit(container)
927
-
928
- if (this._umap.properties.urls.datalayer_versions) {
929
- this.buildVersionsFieldset(container)
930
- }
1026
+ }
931
1027
 
1028
+ _buildAdvancedActions(container) {
932
1029
  const advancedActions = DomUtil.createFieldset(
933
1030
  container,
934
1031
  translate('Advanced actions')
@@ -963,6 +1060,31 @@ export class DataLayer {
963
1060
  }
964
1061
  clone.addEventListener('click', () => this.clone().edit())
965
1062
  if (this.createdOnServer) download.hidden = false
1063
+ }
1064
+
1065
+ edit() {
1066
+ if (!this._umap.editEnabled) {
1067
+ return
1068
+ }
1069
+ const container = DomUtil.create('div', 'umap-layer-properties-container')
1070
+ this._editMetadata(container)
1071
+ this._editLayerProperties(container)
1072
+ this._editShapeProperties(container)
1073
+ this._editAdvancedProperties(container)
1074
+ this._editInteractionProperties(container)
1075
+ this._editTextPathProperties(container)
1076
+ this._editRemoteDataProperties(container)
1077
+ if (!this.isRemoteLayer()) {
1078
+ this._editFields(container)
1079
+ }
1080
+ this.rules.edit(container)
1081
+
1082
+ if (this._umap.properties.urls.datalayer_versions) {
1083
+ this.buildVersionsFieldset(container)
1084
+ }
1085
+
1086
+ this._buildAdvancedActions(container)
1087
+
966
1088
  const backButton = DomUtil.createButtonIcon(
967
1089
  undefined,
968
1090
  'icon-back',
@@ -973,7 +1095,7 @@ export class DataLayer {
973
1095
  DomEvent.disableClickPropagation(backButton)
974
1096
  DomEvent.on(backButton, 'click', this._umap.editDatalayers, this._umap)
975
1097
 
976
- this._umap.editPanel.open({
1098
+ return this._umap.editPanel.open({
977
1099
  content: container,
978
1100
  highlight: 'layers',
979
1101
  actions: [backButton],
@@ -1060,12 +1182,6 @@ export class DataLayer {
1060
1182
  })
1061
1183
  }
1062
1184
 
1063
- featuresToGeoJSON() {
1064
- const features = []
1065
- this.eachFeature((feature) => features.push(feature.toGeoJSON()))
1066
- return features
1067
- }
1068
-
1069
1185
  async show() {
1070
1186
  this._leafletMap.addLayer(this.layer)
1071
1187
  if (!this.isLoaded()) await this.fetchData()
@@ -1078,10 +1194,11 @@ export class DataLayer {
1078
1194
  }
1079
1195
 
1080
1196
  toggle(force) {
1081
- // From now on, do not try to how/hidedataChanged
1082
- // automatically this layer.
1083
- let display = force
1197
+ // From now on, do not try to how/hide
1198
+ // automatically this layer, as user
1199
+ // has taken control on this.
1084
1200
  this._forcedVisibility = true
1201
+ let display = force
1085
1202
  if (force === undefined) {
1086
1203
  if (!this.isVisible()) display = true
1087
1204
  else display = false
@@ -1123,46 +1240,23 @@ export class DataLayer {
1123
1240
  }
1124
1241
 
1125
1242
  count() {
1126
- return this._index.length
1243
+ return this.features.count()
1127
1244
  }
1128
1245
 
1129
1246
  hasData() {
1130
- return !!this._index.length
1247
+ return !!this.count()
1131
1248
  }
1132
1249
 
1133
1250
  isVisible() {
1134
1251
  return Boolean(this.layer && this._leafletMap.hasLayer(this.layer))
1135
1252
  }
1136
1253
 
1137
- getFeatureByIndex(index) {
1138
- if (index === -1) index = this._index.length - 1
1139
- const id = this._index[index]
1140
- return this._features[id]
1141
- }
1142
-
1143
- // TODO Add an index
1144
- // For now, iterate on all the features.
1145
- getFeatureById(id) {
1146
- return Object.values(this._features).find((feature) => feature.id === id)
1147
- }
1148
-
1149
1254
  getNextFeature(feature) {
1150
- const id = this._index.indexOf(stamp(feature))
1151
- const nextId = this._index[id + 1]
1152
- return nextId
1153
- ? this._features[nextId]
1154
- : this.getNextBrowsable().getFeatureByIndex(0)
1255
+ return this.features.next(feature) || this.getNextBrowsable().features.first()
1155
1256
  }
1156
1257
 
1157
1258
  getPreviousFeature(feature) {
1158
- if (this._index <= 1) {
1159
- return null
1160
- }
1161
- const id = this._index.indexOf(stamp(feature))
1162
- const previousId = this._index[id - 1]
1163
- return previousId
1164
- ? this._features[previousId]
1165
- : this.getPreviousBrowsable().getFeatureByIndex(-1)
1259
+ return this.features.prev(feature) || this.getPreviousBrowsable().features.last()
1166
1260
  }
1167
1261
 
1168
1262
  getPreviousBrowsable() {
@@ -1174,11 +1268,9 @@ export class DataLayer {
1174
1268
  }
1175
1269
 
1176
1270
  umapGeoJSON() {
1177
- return {
1178
- type: 'FeatureCollection',
1179
- features: this.isRemoteLayer() ? [] : this.featuresToGeoJSON(),
1180
- _umap_options: this.properties,
1181
- }
1271
+ const geojson = this._umap.formatter.toFeatureCollection(this.features.all())
1272
+ geojson._umap_options = this.properties
1273
+ return geojson
1182
1274
  }
1183
1275
 
1184
1276
  getDOMOrder() {
@@ -1322,8 +1414,27 @@ export class DataLayer {
1322
1414
  )) {
1323
1415
  container.innerHTML = ''
1324
1416
  if (this.layer.renderLegend) return this.layer.renderLegend(container)
1325
- const color = DomUtil.create('span', 'datalayer-color', container)
1326
- color.style.backgroundColor = this.getColor()
1417
+ const keys = new Set(this.fieldKeys)
1418
+ const rules = new Map()
1419
+ for (const rule of this.rules) {
1420
+ rules.set(rule.condition, rule)
1421
+ }
1422
+ for (const rule of this._umap.rules) {
1423
+ if (!rules.has(rule.condition) && keys.has(rule.key)) {
1424
+ rules.set(rule.condition, rule)
1425
+ }
1426
+ }
1427
+ if (rules.size) {
1428
+ const ul = Utils.loadTemplate('<ul class="rules-caption"></ul>')
1429
+ container.appendChild(ul)
1430
+ for (const [_, rule] of rules) {
1431
+ rule.renderLegend(ul)
1432
+ }
1433
+ } else {
1434
+ const color = Utils.loadTemplate('<span class="datalayer-color"></span>')
1435
+ color.style.backgroundColor = this.getColor()
1436
+ container.appendChild(color)
1437
+ }
1327
1438
  }
1328
1439
  }
1329
1440