umap-project 2.9.3__py3-none-any.whl → 3.0.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 (217) hide show
  1. umap/__init__.py +1 -1
  2. umap/context_processors.py +1 -0
  3. umap/forms.py +1 -2
  4. umap/locale/de/LC_MESSAGES/django.mo +0 -0
  5. umap/locale/de/LC_MESSAGES/django.po +218 -96
  6. umap/locale/en/LC_MESSAGES/django.po +128 -52
  7. umap/locale/fr/LC_MESSAGES/django.mo +0 -0
  8. umap/locale/fr/LC_MESSAGES/django.po +128 -52
  9. umap/locale/hu/LC_MESSAGES/django.mo +0 -0
  10. umap/locale/hu/LC_MESSAGES/django.po +209 -88
  11. umap/locale/is/LC_MESSAGES/django.mo +0 -0
  12. umap/locale/is/LC_MESSAGES/django.po +296 -175
  13. umap/migrations/0027_map_tags.py +23 -0
  14. umap/models.py +13 -2
  15. umap/settings/base.py +23 -5
  16. umap/static/umap/base.css +41 -8
  17. umap/static/umap/content.css +72 -37
  18. umap/static/umap/css/bar.css +43 -21
  19. umap/static/umap/css/dialog.css +4 -1
  20. umap/static/umap/css/form.css +40 -27
  21. umap/static/umap/css/icon.css +11 -1
  22. umap/static/umap/css/importers.css +7 -0
  23. umap/static/umap/img/16-white.svg +23 -2
  24. umap/static/umap/img/16.svg +1 -1
  25. umap/static/umap/img/24.svg +4 -4
  26. umap/static/umap/img/home.svg +7 -0
  27. umap/static/umap/img/importers/banfr.svg +1 -0
  28. umap/static/umap/img/marker.svg +2 -5
  29. umap/static/umap/img/source/16-white.svg +24 -3
  30. umap/static/umap/img/source/16.svg +1 -1
  31. umap/static/umap/img/source/24.svg +5 -5
  32. umap/static/umap/img/target.svg +1 -0
  33. umap/static/umap/js/components/alerts/alert.js +0 -1
  34. umap/static/umap/js/modules/browser.js +4 -4
  35. umap/static/umap/js/modules/caption.js +1 -1
  36. umap/static/umap/js/modules/data/features.js +25 -25
  37. umap/static/umap/js/modules/data/layer.js +91 -97
  38. umap/static/umap/js/modules/facets.js +9 -5
  39. umap/static/umap/js/modules/form/builder.js +21 -29
  40. umap/static/umap/js/modules/form/fields.js +40 -14
  41. umap/static/umap/js/modules/formatter.js +1 -1
  42. umap/static/umap/js/modules/global.js +9 -5
  43. umap/static/umap/js/modules/help.js +18 -5
  44. umap/static/umap/js/modules/importer.js +5 -2
  45. umap/static/umap/js/modules/importers/banfr.js +93 -0
  46. umap/static/umap/js/modules/importers/cadastrefr.js +2 -2
  47. umap/static/umap/js/modules/importers/communesfr.js +1 -1
  48. umap/static/umap/js/modules/permissions.js +20 -10
  49. umap/static/umap/js/modules/rendering/icon.js +15 -2
  50. umap/static/umap/js/modules/rendering/layers/classified.js +7 -7
  51. umap/static/umap/js/modules/rendering/layers/cluster.js +2 -2
  52. umap/static/umap/js/modules/rendering/layers/heat.js +4 -4
  53. umap/static/umap/js/modules/rendering/map.js +14 -6
  54. umap/static/umap/js/modules/rendering/popup.js +2 -2
  55. umap/static/umap/js/modules/rendering/template.js +3 -3
  56. umap/static/umap/js/modules/rendering/ui.js +17 -11
  57. umap/static/umap/js/modules/rules.js +13 -16
  58. umap/static/umap/js/modules/schema.js +23 -1
  59. umap/static/umap/js/modules/share.js +1 -1
  60. umap/static/umap/js/modules/slideshow.js +1 -0
  61. umap/static/umap/js/modules/sync/engine.js +141 -19
  62. umap/static/umap/js/modules/sync/undo.js +101 -0
  63. umap/static/umap/js/modules/sync/updaters.js +51 -28
  64. umap/static/umap/js/modules/tableeditor.js +1 -1
  65. umap/static/umap/js/modules/ui/bar.js +61 -21
  66. umap/static/umap/js/modules/ui/tooltip.js +1 -1
  67. umap/static/umap/js/modules/umap.js +190 -176
  68. umap/static/umap/js/modules/utils.js +30 -4
  69. umap/static/umap/js/umap.controls.js +82 -38
  70. umap/static/umap/locale/am_ET.js +11 -6
  71. umap/static/umap/locale/am_ET.json +11 -6
  72. umap/static/umap/locale/ar.js +11 -6
  73. umap/static/umap/locale/ar.json +11 -6
  74. umap/static/umap/locale/ast.js +11 -6
  75. umap/static/umap/locale/ast.json +11 -6
  76. umap/static/umap/locale/bg.js +11 -6
  77. umap/static/umap/locale/bg.json +11 -6
  78. umap/static/umap/locale/br.js +12 -7
  79. umap/static/umap/locale/br.json +12 -7
  80. umap/static/umap/locale/ca.js +11 -6
  81. umap/static/umap/locale/ca.json +11 -6
  82. umap/static/umap/locale/cs_CZ.js +11 -6
  83. umap/static/umap/locale/cs_CZ.json +11 -6
  84. umap/static/umap/locale/da.js +11 -6
  85. umap/static/umap/locale/da.json +11 -6
  86. umap/static/umap/locale/de.js +47 -42
  87. umap/static/umap/locale/de.json +47 -42
  88. umap/static/umap/locale/el.js +11 -6
  89. umap/static/umap/locale/el.json +11 -6
  90. umap/static/umap/locale/en.js +11 -6
  91. umap/static/umap/locale/en.json +11 -6
  92. umap/static/umap/locale/en_US.json +11 -6
  93. umap/static/umap/locale/es.js +11 -6
  94. umap/static/umap/locale/es.json +11 -6
  95. umap/static/umap/locale/et.js +11 -6
  96. umap/static/umap/locale/et.json +11 -6
  97. umap/static/umap/locale/eu.js +11 -6
  98. umap/static/umap/locale/eu.json +11 -6
  99. umap/static/umap/locale/fa_IR.js +11 -6
  100. umap/static/umap/locale/fa_IR.json +11 -6
  101. umap/static/umap/locale/fi.js +11 -6
  102. umap/static/umap/locale/fi.json +11 -6
  103. umap/static/umap/locale/fr.js +11 -6
  104. umap/static/umap/locale/fr.json +11 -6
  105. umap/static/umap/locale/gl.js +12 -7
  106. umap/static/umap/locale/gl.json +12 -7
  107. umap/static/umap/locale/he.js +11 -6
  108. umap/static/umap/locale/he.json +11 -6
  109. umap/static/umap/locale/hr.js +11 -6
  110. umap/static/umap/locale/hr.json +11 -6
  111. umap/static/umap/locale/hu.js +25 -20
  112. umap/static/umap/locale/hu.json +25 -20
  113. umap/static/umap/locale/id.js +11 -6
  114. umap/static/umap/locale/id.json +11 -6
  115. umap/static/umap/locale/is.js +151 -146
  116. umap/static/umap/locale/is.json +151 -146
  117. umap/static/umap/locale/it.js +11 -6
  118. umap/static/umap/locale/it.json +11 -6
  119. umap/static/umap/locale/ja.js +11 -6
  120. umap/static/umap/locale/ja.json +11 -6
  121. umap/static/umap/locale/ko.js +11 -6
  122. umap/static/umap/locale/ko.json +11 -6
  123. umap/static/umap/locale/lt.js +11 -6
  124. umap/static/umap/locale/lt.json +11 -6
  125. umap/static/umap/locale/ms.js +11 -6
  126. umap/static/umap/locale/ms.json +11 -6
  127. umap/static/umap/locale/nl.js +12 -7
  128. umap/static/umap/locale/nl.json +12 -7
  129. umap/static/umap/locale/no.js +11 -6
  130. umap/static/umap/locale/no.json +11 -6
  131. umap/static/umap/locale/pl.js +11 -6
  132. umap/static/umap/locale/pl.json +11 -6
  133. umap/static/umap/locale/pl_PL.json +11 -6
  134. umap/static/umap/locale/pt.js +11 -6
  135. umap/static/umap/locale/pt.json +11 -6
  136. umap/static/umap/locale/pt_BR.js +11 -6
  137. umap/static/umap/locale/pt_BR.json +11 -6
  138. umap/static/umap/locale/pt_PT.js +11 -6
  139. umap/static/umap/locale/pt_PT.json +11 -6
  140. umap/static/umap/locale/ro.js +11 -6
  141. umap/static/umap/locale/ro.json +11 -6
  142. umap/static/umap/locale/ru.js +11 -6
  143. umap/static/umap/locale/ru.json +11 -6
  144. umap/static/umap/locale/sk_SK.js +11 -6
  145. umap/static/umap/locale/sk_SK.json +11 -6
  146. umap/static/umap/locale/sl.js +11 -6
  147. umap/static/umap/locale/sl.json +11 -6
  148. umap/static/umap/locale/sr.js +11 -6
  149. umap/static/umap/locale/sr.json +11 -6
  150. umap/static/umap/locale/sv.js +11 -6
  151. umap/static/umap/locale/sv.json +11 -6
  152. umap/static/umap/locale/th_TH.js +11 -6
  153. umap/static/umap/locale/th_TH.json +11 -6
  154. umap/static/umap/locale/tr.js +11 -6
  155. umap/static/umap/locale/tr.json +11 -6
  156. umap/static/umap/locale/uk_UA.js +11 -6
  157. umap/static/umap/locale/uk_UA.json +11 -6
  158. umap/static/umap/locale/vi.js +11 -6
  159. umap/static/umap/locale/vi.json +11 -6
  160. umap/static/umap/locale/vi_VN.json +11 -6
  161. umap/static/umap/locale/zh.js +11 -6
  162. umap/static/umap/locale/zh.json +11 -6
  163. umap/static/umap/locale/zh_CN.json +11 -6
  164. umap/static/umap/locale/zh_TW.Big5.json +11 -6
  165. umap/static/umap/locale/zh_TW.js +19 -14
  166. umap/static/umap/locale/zh_TW.json +19 -14
  167. umap/static/umap/map.css +58 -28
  168. umap/static/umap/unittests/sync.js +0 -57
  169. umap/static/umap/unittests/utils.js +47 -0
  170. umap/static/umap/vars.css +5 -2
  171. umap/static/umap/vendors/photon/leaflet.photon.js +3 -0
  172. umap/sync/payloads.py +3 -2
  173. umap/templates/auth/user_detail.html +1 -1
  174. umap/templates/auth/user_stars.html +1 -1
  175. umap/templates/umap/content.html +17 -12
  176. umap/templates/umap/home.html +7 -5
  177. umap/templates/umap/map_fragment.html +1 -1
  178. umap/templates/umap/map_list.html +20 -13
  179. umap/templates/umap/search.html +7 -3
  180. umap/templates/umap/search_bar.html +13 -11
  181. umap/templates/umap/team_detail.html +1 -1
  182. umap/tests/base.py +2 -1
  183. umap/tests/fixtures/remote_data.umap +55 -0
  184. umap/tests/fixtures/test_upload_data_with_iconurl.umap +122 -0
  185. umap/tests/integration/test_browser.py +1 -3
  186. umap/tests/integration/test_conditional_rules.py +3 -0
  187. umap/tests/integration/test_edit_datalayer.py +2 -7
  188. umap/tests/integration/test_edit_map.py +15 -0
  189. umap/tests/integration/test_edit_polygon.py +1 -2
  190. umap/tests/integration/test_import.py +59 -2
  191. umap/tests/integration/test_optimistic_merge.py +4 -3
  192. umap/tests/integration/test_owned_map.py +0 -1
  193. umap/tests/integration/test_save.py +2 -4
  194. umap/tests/integration/test_undo_redo.py +267 -0
  195. umap/tests/integration/test_websocket_sync.py +78 -11
  196. umap/tests/settings.py +1 -3
  197. umap/tests/test_datalayer_s3.py +1 -0
  198. umap/tests/test_map_views.py +1 -0
  199. umap/tests/test_views.py +34 -0
  200. umap/utils.py +1 -1
  201. umap/views.py +23 -2
  202. {umap_project-2.9.3.dist-info → umap_project-3.0.0.dist-info}/METADATA +13 -12
  203. {umap_project-2.9.3.dist-info → umap_project-3.0.0.dist-info}/RECORD +206 -208
  204. umap/static/umap/js/modules/saving.js +0 -52
  205. umap/static/umap/test/.eslintrc +0 -21
  206. umap/static/umap/test/DataLayer.js +0 -463
  207. umap/static/umap/test/Feature.js +0 -131
  208. umap/static/umap/test/Map.js +0 -37
  209. umap/static/umap/test/Marker.js +0 -126
  210. umap/static/umap/test/Polygon.js +0 -111
  211. umap/static/umap/test/Polyline.js +0 -286
  212. umap/static/umap/test/Util.js +0 -28
  213. umap/static/umap/test/_pre.js +0 -455
  214. umap/static/umap/test/index.html +0 -139
  215. {umap_project-2.9.3.dist-info → umap_project-3.0.0.dist-info}/WHEEL +0 -0
  216. {umap_project-2.9.3.dist-info → umap_project-3.0.0.dist-info}/entry_points.txt +0 -0
  217. {umap_project-2.9.3.dist-info → umap_project-3.0.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,26 +1,25 @@
1
1
  // FIXME: this module should not depend on Leaflet
2
2
  import {
3
- DomUtil,
4
3
  DomEvent,
5
- stamp,
4
+ DomUtil,
6
5
  GeoJSON,
6
+ stamp,
7
7
  } from '../../../vendors/leaflet/leaflet-src.esm.js'
8
- import * as Utils from '../utils.js'
9
- import { Default as DefaultLayer } from '../rendering/layers/base.js'
10
- import { Cluster } from '../rendering/layers/cluster.js'
11
- import { Heat } from '../rendering/layers/heat.js'
12
- import { Categorized, Choropleth, Circles } from '../rendering/layers/classified.js'
13
8
  import {
14
9
  uMapAlert as Alert,
15
10
  uMapAlertConflict as AlertConflict,
16
11
  } from '../../components/alerts/alert.js'
12
+ import { MutatingForm } from '../form/builder.js'
17
13
  import { translate } from '../i18n.js'
18
14
  import { DataLayerPermissions } from '../permissions.js'
19
- import { Point, LineString, Polygon } from './features.js'
20
- import TableEditor from '../tableeditor.js'
21
- import { ServerStored } from '../saving.js'
15
+ import { Default as DefaultLayer } from '../rendering/layers/base.js'
16
+ import { Categorized, Choropleth, Circles } from '../rendering/layers/classified.js'
17
+ import { Cluster } from '../rendering/layers/cluster.js'
18
+ import { Heat } from '../rendering/layers/heat.js'
22
19
  import * as Schema from '../schema.js'
23
- import { MutatingForm } from '../form/builder.js'
20
+ import TableEditor from '../tableeditor.js'
21
+ import * as Utils from '../utils.js'
22
+ import { LineString, Point, Polygon } from './features.js'
24
23
 
25
24
  export const LAYER_TYPES = [
26
25
  DefaultLayer,
@@ -36,9 +35,8 @@ const LAYER_MAP = LAYER_TYPES.reduce((acc, klass) => {
36
35
  return acc
37
36
  }, {})
38
37
 
39
- export class DataLayer extends ServerStored {
38
+ export class DataLayer {
40
39
  constructor(umap, leafletMap, data = {}) {
41
- super()
42
40
  this._umap = umap
43
41
  this.sync = umap.syncEngine.proxy(this)
44
42
  this._index = Array()
@@ -49,7 +47,6 @@ export class DataLayer extends ServerStored {
49
47
  this._leafletMap = leafletMap
50
48
  this.parentPane = this._leafletMap.getPane('overlayPane')
51
49
  this.pane = this._leafletMap.createPane(`datalayer${stamp(this)}`, this.parentPane)
52
- this.pane.dataset.id = stamp(this)
53
50
  // FIXME: should be on layer
54
51
  this.renderer = L.svg({ pane: this.pane })
55
52
  this.defaultOptions = {
@@ -66,6 +63,7 @@ export class DataLayer extends ServerStored {
66
63
  data.id = data.id || crypto.randomUUID()
67
64
 
68
65
  this.setOptions(data)
66
+ this.pane.dataset.id = this.id
69
67
 
70
68
  if (!Utils.isObject(this.options.remoteData)) {
71
69
  this.options.remoteData = {}
@@ -83,7 +81,7 @@ export class DataLayer extends ServerStored {
83
81
  this.connectToMap()
84
82
  this.permissions = new DataLayerPermissions(this._umap, this)
85
83
 
86
- this._needsFetch = this.createdOnServer
84
+ this._needsFetch = this.createdOnServer || this.isRemoteLayer()
87
85
  if (!this.createdOnServer) {
88
86
  if (this.showAtLoad()) this.show()
89
87
  }
@@ -114,7 +112,6 @@ export class DataLayer extends ServerStored {
114
112
 
115
113
  set isDeleted(status) {
116
114
  this._isDeleted = status
117
- if (status) this.isDirty = status
118
115
  }
119
116
 
120
117
  get isDeleted() {
@@ -144,11 +141,10 @@ export class DataLayer extends ServerStored {
144
141
  if (fields.includes('options.type')) {
145
142
  this.resetLayer()
146
143
  }
147
- this.hide()
148
144
  for (const field of fields) {
149
145
  this.layer.onEdit(field, builder)
150
146
  }
151
- this.show()
147
+ this.redraw()
152
148
  break
153
149
  case 'remote-data':
154
150
  this.fetchRemoteData()
@@ -265,18 +261,19 @@ export class DataLayer extends ServerStored {
265
261
  if (geojson._storage) geojson._umap_options = geojson._storage // Retrocompat
266
262
  geojson._umap_options.id = this.id
267
263
  if (geojson._umap_options) this.setOptions(geojson._umap_options)
268
- if (this.isRemoteLayer()) await this.fetchRemoteData()
269
- else this.fromGeoJSON(geojson, false)
264
+ if (this.isRemoteLayer()) {
265
+ await this.fetchRemoteData()
266
+ } else {
267
+ this.fromGeoJSON(geojson, false)
268
+ }
270
269
  }
271
270
 
272
271
  clear() {
273
- this.layer.clearLayers()
274
- this._features = {}
275
- this._index = Array()
276
- if (this._geojson) {
277
- this.backupData()
278
- this._geojson = null
272
+ this.sync.startBatch()
273
+ for (const feature of Object.values(this._features)) {
274
+ feature.del()
279
275
  }
276
+ this.sync.commitBatch()
280
277
  this.dataChanged()
281
278
  }
282
279
 
@@ -330,8 +327,9 @@ export class DataLayer extends ServerStored {
330
327
  this.clear()
331
328
  return this._umap.formatter
332
329
  .parse(raw, this.options.remoteData.format)
333
- .then((geojson) => this.fromGeoJSON(geojson))
330
+ .then((geojson) => this.fromGeoJSON(geojson, false))
334
331
  .catch((error) => {
332
+ console.debug(error)
335
333
  Alert.error(
336
334
  translate('Cannot parse remote data for layer "{layer}" with url "{url}"', {
337
335
  layer: this.getName(),
@@ -366,9 +364,8 @@ export class DataLayer extends ServerStored {
366
364
  }
367
365
 
368
366
  connectToMap() {
369
- const id = stamp(this)
370
- if (!this._umap.datalayers[id]) {
371
- this._umap.datalayers[id] = this
367
+ if (!this._umap.datalayers[this.id]) {
368
+ this._umap.datalayers[this.id] = this
372
369
  }
373
370
  if (!this._umap.datalayersIndex.includes(this)) {
374
371
  this._umap.datalayersIndex.push(this)
@@ -417,7 +414,10 @@ export class DataLayer extends ServerStored {
417
414
 
418
415
  removeFeature(feature, sync) {
419
416
  const id = stamp(feature)
420
- if (sync !== false) feature.sync.delete()
417
+ if (sync !== false) {
418
+ const oldValue = feature.toGeoJSON()
419
+ feature.sync.delete(oldValue)
420
+ }
421
421
  this.hideFeature(feature)
422
422
  delete this._umap.featuresIndex[feature.getSlug()]
423
423
  feature.disconnectFromDataLayer(this)
@@ -518,7 +518,7 @@ export class DataLayer extends ServerStored {
518
518
  }
519
519
  if (feature && !feature.isEmpty()) {
520
520
  this.addFeature(feature)
521
- if (sync) feature.onCommit()
521
+ if (sync) feature.sync.upsert(feature.toGeoJSON(), null)
522
522
  return feature
523
523
  }
524
524
  }
@@ -526,12 +526,14 @@ export class DataLayer extends ServerStored {
526
526
  async importRaw(raw, format) {
527
527
  return this._umap.formatter
528
528
  .parse(raw, format)
529
- .then((geojson) => this.addData(geojson))
530
- .then((data) => {
531
- if (data?.length) this.isDirty = true
529
+ .then((geojson) => {
530
+ this.sync.startBatch()
531
+ const data = this.addData(geojson)
532
+ this.sync.commitBatch()
532
533
  return data
533
534
  })
534
535
  .catch((error) => {
536
+ console.debug(error)
535
537
  Alert.error(translate('Import failed: invalid data'))
536
538
  })
537
539
  }
@@ -595,17 +597,17 @@ export class DataLayer extends ServerStored {
595
597
  }
596
598
 
597
599
  del(sync = true) {
600
+ const oldValue = Utils.CopyJSON(this.umapGeoJSON())
598
601
  this.erase()
599
602
  if (sync) {
600
603
  this.isDeleted = true
601
- this.sync.delete()
604
+ this.sync.delete(oldValue)
602
605
  }
603
606
  }
604
607
 
605
608
  empty() {
606
609
  if (this.isRemoteLayer()) return
607
610
  this.clear()
608
- this.isDirty = true
609
611
  }
610
612
 
611
613
  clone() {
@@ -629,29 +631,9 @@ export class DataLayer extends ServerStored {
629
631
  this.clear()
630
632
  }
631
633
 
632
- reset() {
633
- if (!this.createdOnServer) {
634
- this.erase()
635
- return
636
- }
637
-
638
- this.resetOptions()
639
- this.parentPane.appendChild(this.pane)
640
- if (this._leaflet_events_bk && !this._leaflet_events) {
641
- this._leaflet_events = this._leaflet_events_bk
642
- }
643
- this.clear()
644
- this.hide()
645
- if (this.isRemoteLayer()) this.fetchRemoteData()
646
- else if (this._geojson_bk) this.fromGeoJSON(this._geojson_bk)
647
- this.show()
648
- this.isDirty = false
649
- }
650
-
651
634
  redraw() {
652
635
  if (!this.isVisible()) return
653
- this.hide()
654
- this.show()
636
+ this.eachFeature((feature) => feature.redraw())
655
637
  }
656
638
 
657
639
  edit() {
@@ -831,8 +813,9 @@ export class DataLayer extends ServerStored {
831
813
  this
832
814
  )
833
815
 
834
- if (this._umap.properties.urls.datalayer_versions)
816
+ if (this._umap.properties.urls.datalayer_versions) {
835
817
  this.buildVersionsFieldset(container)
818
+ }
836
819
 
837
820
  const advancedActions = DomUtil.createFieldset(
838
821
  container,
@@ -907,10 +890,15 @@ export class DataLayer extends ServerStored {
907
890
  const appendVersion = (data) => {
908
891
  const date = new Date(Number.parseInt(data.at, 10))
909
892
  const content = `${date.toLocaleString(U.lang)} (${Number.parseInt(data.size) / 1000}Kb)`
910
- const el = DomUtil.create('div', 'umap-datalayer-version', versionsContainer)
911
- const button = DomUtil.createButton('', el, '', () => this.restore(data.ref))
912
- button.title = translate('Restore this version')
913
- DomUtil.add('span', '', el, content)
893
+ const [el, { button }] = Utils.loadTemplateWithRefs(
894
+ `<div class="umap-datalayer-version">
895
+ <button type="button" title="${translate('Restore this version')}" data-ref=button>
896
+ <i class="icon icon-16 icon-restore"></i> ${content}
897
+ </button>
898
+ </div>`
899
+ )
900
+ versionsContainer.appendChild(el)
901
+ button.addEventListener('click', () => this.restore(data.ref))
914
902
  }
915
903
 
916
904
  const versionsContainer = DomUtil.createFieldset(container, translate('Versions'), {
@@ -934,11 +922,19 @@ export class DataLayer extends ServerStored {
934
922
  )
935
923
  if (!error) {
936
924
  if (geojson._storage) geojson._umap_options = geojson._storage // Retrocompat.
937
- if (geojson._umap_options) this.setOptions(geojson._umap_options)
925
+ if (geojson._umap_options) {
926
+ const oldOptions = Utils.CopyJSON(this.options)
927
+ this.setOptions(geojson._umap_options)
928
+ this.sync.update('options', this.options, oldOptions)
929
+ }
938
930
  this.empty()
939
- if (this.isRemoteLayer()) this.fetchRemoteData()
940
- else this.addData(geojson)
941
- this.isDirty = true
931
+ if (this.isRemoteLayer()) {
932
+ this.fetchRemoteData()
933
+ } else {
934
+ this.sync.startBatch()
935
+ this.addData(geojson)
936
+ this.sync.commitBatch()
937
+ }
942
938
  }
943
939
  })
944
940
  }
@@ -960,12 +956,18 @@ export class DataLayer extends ServerStored {
960
956
  this.propagateHide()
961
957
  }
962
958
 
963
- toggle() {
959
+ toggle(force) {
964
960
  // From now on, do not try to how/hidedataChanged
965
961
  // automatically this layer.
962
+ let display = force
966
963
  this._forcedVisibility = true
967
- if (!this.isVisible()) this.show()
964
+ if (force === undefined) {
965
+ if (!this.isVisible()) display = true
966
+ else display = false
967
+ }
968
+ if (display) this.show()
968
969
  else this.hide()
970
+ this._umap.bottomBar.redraw()
969
971
  }
970
972
 
971
973
  zoomTo() {
@@ -1086,7 +1088,11 @@ export class DataLayer extends ServerStored {
1086
1088
 
1087
1089
  setReferenceVersion({ response, sync }) {
1088
1090
  this._referenceVersion = response.headers.get('X-Datalayer-Version')
1089
- if (sync) this.sync.update('_referenceVersion', this._referenceVersion)
1091
+ if (sync) {
1092
+ this.sync.update('_referenceVersion', this._referenceVersion, null, {
1093
+ undo: false,
1094
+ })
1095
+ }
1090
1096
  }
1091
1097
 
1092
1098
  async save() {
@@ -1115,6 +1121,10 @@ export class DataLayer extends ServerStored {
1115
1121
  }
1116
1122
 
1117
1123
  async _trySave(url, headers, formData) {
1124
+ if (this._forceSave) {
1125
+ headers = {}
1126
+ this._forceSave = false
1127
+ }
1118
1128
  const [data, response, error] = await this._umap.server.post(url, headers, formData)
1119
1129
  if (error) {
1120
1130
  if (response && response.status === 412) {
@@ -1124,15 +1134,8 @@ export class DataLayer extends ServerStored {
1124
1134
  'This situation is tricky, you have to choose carefully which version is pertinent.'
1125
1135
  ),
1126
1136
  async () => {
1127
- // Save again this layer
1128
- const status = await this._trySave(url, {}, formData)
1129
- if (status) {
1130
- this.isDirty = false
1131
-
1132
- // Call the main save, in case something else needs to be saved
1133
- // as the conflict stopped the saving flow
1134
- await this._umap.saveAll()
1135
- }
1137
+ this._forceSave = true
1138
+ await this._umap.saveAll()
1136
1139
  }
1137
1140
  )
1138
1141
  }
@@ -1167,7 +1170,7 @@ export class DataLayer extends ServerStored {
1167
1170
  }
1168
1171
 
1169
1172
  commitDelete() {
1170
- delete this._umap.datalayers[stamp(this)]
1173
+ delete this._umap.datalayers[this.id]
1171
1174
  }
1172
1175
 
1173
1176
  getName() {
@@ -1234,25 +1237,16 @@ export class DataLayer extends ServerStored {
1234
1237
  translate('Delete layer')
1235
1238
  )
1236
1239
  if (this.isReadOnly()) {
1237
- DomUtil.addClass(container, 'readonly')
1240
+ container.classList.add('readonly')
1238
1241
  } else {
1239
- DomEvent.on(edit, 'click', this.edit, this)
1240
- DomEvent.on(table, 'click', this.tableEdit, this)
1241
- DomEvent.on(
1242
- remove,
1243
- 'click',
1244
- function () {
1245
- if (!this.isVisible()) return
1246
- this._umap.dialog
1247
- .confirm(translate('Are you sure you want to delete this layer?'))
1248
- .then(() => {
1249
- this.del()
1250
- })
1251
- },
1252
- this
1253
- )
1242
+ edit.addEventListener('click', () => this.edit())
1243
+ table.addEventListener('click', () => this.tableEdit())
1244
+ remove.addEventListener('click', () => {
1245
+ if (!this.isVisible()) return
1246
+ this.del()
1247
+ })
1254
1248
  }
1255
- DomEvent.on(toggle, 'click', this.toggle, this)
1249
+ DomEvent.on(toggle, 'click', () => this.toggle())
1256
1250
  DomEvent.on(zoomTo, 'click', this.zoomTo, this)
1257
1251
  container.classList.add(this.getHidableClass())
1258
1252
  container.classList.toggle('off', !this.isVisible())
@@ -135,7 +135,13 @@ export default class Facets {
135
135
  for (const [property, { label, type }] of parsed) {
136
136
  dumped.push([property, label, type].filter(Boolean).join('|'))
137
137
  }
138
- return dumped.join(',')
138
+ const oldValue = this._umap.properties.facetKey
139
+ this._umap.properties.facetKey = dumped.join(',')
140
+ this._umap.sync.update(
141
+ 'properties.facetKey',
142
+ this._umap.properties.facetKey,
143
+ oldValue
144
+ )
139
145
  }
140
146
 
141
147
  has(property) {
@@ -146,15 +152,13 @@ export default class Facets {
146
152
  const defined = this.getDefined()
147
153
  if (!defined.has(property)) {
148
154
  defined.set(property, { label, type })
149
- this._umap.properties.facetKey = this.dumps(defined)
150
- this._umap.isDirty = true
155
+ this.dumps(defined)
151
156
  }
152
157
  }
153
158
 
154
159
  remove(property) {
155
160
  const defined = this.getDefined()
156
161
  defined.delete(property)
157
- this._umap.properties.facetKey = this.dumps(defined)
158
- this._umap.isDirty = true
162
+ this.dumps(defined)
159
163
  }
160
164
  }
@@ -1,7 +1,7 @@
1
- import getClass from './fields.js'
2
- import * as Utils from '../utils.js'
3
- import { SCHEMA } from '../schema.js'
4
1
  import { translate } from '../i18n.js'
2
+ import { SCHEMA } from '../schema.js'
3
+ import * as Utils from '../utils.js'
4
+ import getClass from './fields.js'
5
5
 
6
6
  export class Form extends Utils.WithEvents {
7
7
  constructor(obj, fields, properties) {
@@ -70,21 +70,7 @@ export class Form extends Utils.WithEvents {
70
70
  }
71
71
 
72
72
  setter(field, value) {
73
- const path = field.split('.')
74
- let obj = this.obj
75
- let what
76
- for (let i = 0, l = path.length; i < l; i++) {
77
- what = path[i]
78
- if (what === path[l - 1]) {
79
- if (typeof value === 'undefined') {
80
- delete obj[what]
81
- } else {
82
- obj[what] = value
83
- }
84
- } else {
85
- obj = obj[what]
86
- }
87
- }
73
+ Utils.setObjectValue(this.obj, field, value)
88
74
  }
89
75
 
90
76
  restoreField(field) {
@@ -144,14 +130,8 @@ export class MutatingForm extends Form {
144
130
  }
145
131
  for (const [key, defaults] of Object.entries(SCHEMA)) {
146
132
  const properties = Object.assign({}, defaults)
147
- if (properties.type === Boolean) {
148
- if (properties.nullable) properties.handler = 'NullableChoices'
149
- else properties.handler = 'Switch'
150
- } else if (properties.type === 'Text') {
151
- properties.handler = 'Textarea'
152
- } else if (properties.type === Number) {
153
- if (properties.step) properties.handler = 'Range'
154
- else properties.handler = 'IntInput'
133
+ if (properties.type === Array) {
134
+ properties.handler = 'CheckBoxes'
155
135
  } else if (properties.choices) {
156
136
  const text_length = properties.choices.reduce(
157
137
  (acc, [_, label]) => acc + label.length,
@@ -165,6 +145,14 @@ export class MutatingForm extends Form {
165
145
  properties.handler = 'Select'
166
146
  properties.selectOptions = properties.choices
167
147
  }
148
+ } else if (properties.type === Boolean) {
149
+ if (properties.nullable) properties.handler = 'NullableChoices'
150
+ else properties.handler = 'Switch'
151
+ } else if (properties.type === 'Text') {
152
+ properties.handler = 'Textarea'
153
+ } else if (properties.type === Number) {
154
+ if (properties.step) properties.handler = 'Range'
155
+ else properties.handler = 'IntInput'
168
156
  } else {
169
157
  switch (key) {
170
158
  case 'color':
@@ -190,13 +178,17 @@ export class MutatingForm extends Form {
190
178
  }
191
179
 
192
180
  setter(field, value) {
193
- super.setter(field, value)
194
- this.obj.isDirty = true
181
+ const oldValue = this.getter(field)
182
+ if ('setter' in this.obj) {
183
+ this.obj.setter(field, value)
184
+ } else {
185
+ super.setter(field, value)
186
+ }
195
187
  if ('render' in this.obj) {
196
188
  this.obj.render([field], this)
197
189
  }
198
190
  if ('sync' in this.obj) {
199
- this.obj.sync.update(field, value)
191
+ this.obj.sync.update(field, value, oldValue)
200
192
  }
201
193
  }
202
194
 
@@ -1,12 +1,12 @@
1
- import * as Utils from '../utils.js'
2
- import { translate } from '../i18n.js'
3
1
  import {
4
2
  AjaxAutocomplete,
5
3
  AjaxAutocompleteMultiple,
6
4
  AutocompleteDatalist,
7
5
  } from '../autocomplete.js'
8
- import { SCHEMA } from '../schema.js'
6
+ import { translate } from '../i18n.js'
9
7
  import * as Icon from '../rendering/icon.js'
8
+ import { SCHEMA } from '../schema.js'
9
+ import * as Utils from '../utils.js'
10
10
 
11
11
  const Fields = {}
12
12
 
@@ -152,7 +152,9 @@ Fields.Textarea = class extends BaseElement {
152
152
  this.textarea.value = value
153
153
  }
154
154
  }
155
-
155
+ clear() {
156
+ this.textarea.value = ''
157
+ }
156
158
  value() {
157
159
  return this.textarea.value
158
160
  }
@@ -254,8 +256,8 @@ Fields.BlurInput = class extends Fields.Input {
254
256
  const IntegerMixin = (Base) =>
255
257
  class extends Base {
256
258
  value() {
257
- return !isNaN(this.input.value) && this.input.value !== ''
258
- ? parseInt(this.input.value, 10)
259
+ return !Number.isNaN(this.input.value) && this.input.value !== ''
260
+ ? Number.parseInt(this.input.value, 10)
259
261
  : undefined
260
262
  }
261
263
 
@@ -270,8 +272,8 @@ Fields.BlurIntInput = class extends IntegerMixin(Fields.BlurInput) {}
270
272
  const FloatMixin = (Base) =>
271
273
  class extends Base {
272
274
  value() {
273
- return !isNaN(this.input.value) && this.input.value !== ''
274
- ? parseFloat(this.input.value)
275
+ return !Number.isNaN(this.input.value) && this.input.value !== ''
276
+ ? Number.parseFloat(this.input.value)
275
277
  : undefined
276
278
  }
277
279
 
@@ -324,6 +326,29 @@ Fields.CheckBox = class extends BaseElement {
324
326
  }
325
327
  }
326
328
 
329
+ Fields.CheckBoxes = class extends BaseElement {
330
+ getInputTemplate(value, label) {
331
+ return `<label><input type=checkbox value="${value}" name="${this.name}" data-ref=input />${label}</label>`
332
+ }
333
+
334
+ build() {
335
+ const initial = this.get() || []
336
+ for (const [value, label] of this.properties.choices) {
337
+ const [root, { input }] = Utils.loadTemplateWithRefs(
338
+ this.getInputTemplate(value, label)
339
+ )
340
+ this.container.appendChild(root)
341
+ input.checked = initial.includes(value)
342
+ input.addEventListener('change', () => this.sync())
343
+ }
344
+ super.build()
345
+ }
346
+
347
+ value() {
348
+ return Array.from(this.root.querySelectorAll('input:checked')).map((el) => el.value)
349
+ }
350
+ }
351
+
327
352
  Fields.Select = class extends BaseElement {
328
353
  getTemplate() {
329
354
  return `<select name="${this.name}" data-ref=select></select>`
@@ -390,7 +415,7 @@ Fields.Select = class extends BaseElement {
390
415
 
391
416
  Fields.IntSelect = class extends Fields.Select {
392
417
  value() {
393
- return parseInt(super.value(), 10)
418
+ return Number.parseInt(super.value(), 10)
394
419
  }
395
420
  }
396
421
 
@@ -541,14 +566,14 @@ Fields.DataLayerSwitcher = class extends Fields.Select {
541
566
  !datalayer.isDataReadOnly() &&
542
567
  datalayer.isBrowsable()
543
568
  ) {
544
- options.push([L.stamp(datalayer), datalayer.getName()])
569
+ options.push([datalayer.id, datalayer.getName()])
545
570
  }
546
571
  })
547
572
  return options
548
573
  }
549
574
 
550
575
  toHTML() {
551
- return L.stamp(this.obj.datalayer)
576
+ return this.obj.datalayer.id
552
577
  }
553
578
 
554
579
  toJS() {
@@ -805,7 +830,7 @@ Fields.IconUrl = class extends Fields.BlurInput {
805
830
  categories[category] = categories[category] || []
806
831
  categories[category].push(props)
807
832
  }
808
- const sorted = Object.entries(categories).toSorted(([a], [b]) =>
833
+ const sorted = Object.entries(categories).sort(([a], [b]) =>
809
834
  Utils.naturalSort(a, b, U.lang)
810
835
  )
811
836
  for (const [name, items] of sorted) {
@@ -1296,12 +1321,13 @@ Fields.ManageEditors = class extends BaseElement {
1296
1321
  placeholder: translate("Type editor's username"),
1297
1322
  }
1298
1323
  this.autocomplete = new AjaxAutocompleteMultiple(this.container, options)
1299
- this._values = this.toHTML()
1300
- if (this._values)
1324
+ this._values = this.toHTML() || []
1325
+ if (this._values) {
1301
1326
  for (let i = 0; i < this._values.length; i++)
1302
1327
  this.autocomplete.displaySelected({
1303
1328
  item: { value: this._values[i].id, label: this._values[i].name },
1304
1329
  })
1330
+ }
1305
1331
  }
1306
1332
 
1307
1333
  value() {
@@ -1,6 +1,6 @@
1
+ import { uMapAlert as Alert } from '../components/alerts/alert.js'
1
2
  /* Uses globals for: csv2geojson, osmtogeojson (not available as ESM) */
2
3
  import { translate } from './i18n.js'
3
- import { uMapAlert as Alert } from '../components/alerts/alert.js'
4
4
 
5
5
  export const EXPORT_FORMATS = {
6
6
  geojson: {
@@ -1,13 +1,17 @@
1
1
  import { uMapAlert as Alert } from '../components/alerts/alert.js'
2
- import { AjaxAutocomplete, AjaxAutocompleteMultiple, AutocompleteDatalist } from './autocomplete.js'
2
+ import {
3
+ AjaxAutocomplete,
4
+ AjaxAutocompleteMultiple,
5
+ AutocompleteDatalist,
6
+ } from './autocomplete.js'
7
+ import { LineString, Point, Polygon } from './data/features.js'
8
+ import { LAYER_TYPES } from './data/layer.js'
3
9
  import Help from './help.js'
10
+ import * as Icon from './rendering/icon.js'
11
+ import { LeafletMarker, LeafletPolygon, LeafletPolyline } from './rendering/ui.js'
4
12
  import { ServerRequest } from './request.js'
5
13
  import { SCHEMA } from './schema.js'
6
14
  import * as Utils from './utils.js'
7
- import * as Icon from './rendering/icon.js'
8
- import { LAYER_TYPES } from './data/layer.js'
9
- import { Point, LineString, Polygon } from './data/features.js'
10
- import { LeafletMarker, LeafletPolyline, LeafletPolygon } from './rendering/ui.js'
11
15
 
12
16
  // Import modules and export them to the global scope.
13
17
  // For the not yet module-compatible JS out there.