umap-project 2.3.0__py3-none-any.whl → 2.4.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 (211) hide show
  1. umap/__init__.py +1 -1
  2. umap/locale/en/LC_MESSAGES/django.po +81 -31
  3. umap/locale/fr/LC_MESSAGES/django.mo +0 -0
  4. umap/locale/fr/LC_MESSAGES/django.po +117 -66
  5. umap/locale/pl/LC_MESSAGES/django.mo +0 -0
  6. umap/locale/pl/LC_MESSAGES/django.po +83 -78
  7. umap/locale/pt/LC_MESSAGES/django.mo +0 -0
  8. umap/locale/pt/LC_MESSAGES/django.po +129 -123
  9. umap/management/commands/run_websocket_server.py +23 -0
  10. umap/models.py +6 -1
  11. umap/settings/base.py +11 -3
  12. umap/static/umap/base.css +68 -186
  13. umap/static/umap/content.css +3 -2
  14. umap/static/umap/css/dialog.css +18 -0
  15. umap/static/umap/css/icon.css +8 -0
  16. umap/static/umap/css/importers.css +51 -0
  17. umap/static/umap/css/panel.css +18 -57
  18. umap/static/umap/css/tooltip.css +59 -0
  19. umap/static/umap/css/window.css +35 -0
  20. umap/static/umap/img/16-white.svg +1 -3
  21. umap/static/umap/img/alert-icon-error.svg +8 -0
  22. umap/static/umap/img/alert-icon-info.svg +4 -0
  23. umap/static/umap/img/alert-icon-success.svg +3 -0
  24. umap/static/umap/img/icon-external-link.svg +3 -0
  25. umap/static/umap/img/importers/communesfr.svg +5 -0
  26. umap/static/umap/img/importers/datasets.svg +13 -0
  27. umap/static/umap/img/importers/geodatamine.svg +10 -0
  28. umap/static/umap/img/importers/overpass.svg +7 -0
  29. umap/static/umap/img/importers/random.svg +18 -0
  30. umap/static/umap/img/importers/random1.svg +4 -0
  31. umap/static/umap/img/importers/random2.svg +4 -0
  32. umap/static/umap/img/source/16-white.svg +2 -4
  33. umap/static/umap/js/components/alerts/alert.css +160 -0
  34. umap/static/umap/js/components/alerts/alert.js +169 -0
  35. umap/static/umap/js/components/base.js +54 -0
  36. umap/static/umap/js/modules/autocomplete.js +347 -0
  37. umap/static/umap/js/modules/browser.js +14 -21
  38. umap/static/umap/js/modules/caption.js +119 -0
  39. umap/static/umap/js/modules/global.js +37 -11
  40. umap/static/umap/js/modules/help.js +255 -0
  41. umap/static/umap/js/modules/importer.js +308 -0
  42. umap/static/umap/js/modules/importers/communesfr.js +44 -0
  43. umap/static/umap/js/modules/importers/datasets.js +42 -0
  44. umap/static/umap/js/modules/importers/geodatamine.js +95 -0
  45. umap/static/umap/js/modules/importers/overpass.js +84 -0
  46. umap/static/umap/js/modules/request.js +12 -14
  47. umap/static/umap/js/modules/rules.js +241 -0
  48. umap/static/umap/js/modules/schema.js +63 -14
  49. umap/static/umap/js/modules/sync/engine.js +93 -0
  50. umap/static/umap/js/modules/sync/updaters.js +109 -0
  51. umap/static/umap/js/modules/sync/websocket.js +25 -0
  52. umap/static/umap/js/modules/ui/dialog.js +52 -0
  53. umap/static/umap/js/modules/{panel.js → ui/panel.js} +37 -20
  54. umap/static/umap/js/modules/ui/tooltip.js +116 -0
  55. umap/static/umap/js/modules/utils.js +25 -18
  56. umap/static/umap/js/umap.controls.js +37 -112
  57. umap/static/umap/js/umap.core.js +1 -327
  58. umap/static/umap/js/umap.features.js +77 -29
  59. umap/static/umap/js/umap.forms.js +17 -19
  60. umap/static/umap/js/umap.js +265 -228
  61. umap/static/umap/js/umap.layer.js +154 -76
  62. umap/static/umap/js/umap.permissions.js +5 -9
  63. umap/static/umap/js/umap.popup.js +2 -1
  64. umap/static/umap/js/umap.tableeditor.js +8 -8
  65. umap/static/umap/locale/am_ET.js +51 -16
  66. umap/static/umap/locale/am_ET.json +51 -16
  67. umap/static/umap/locale/ar.js +51 -16
  68. umap/static/umap/locale/ar.json +51 -16
  69. umap/static/umap/locale/ast.js +51 -16
  70. umap/static/umap/locale/ast.json +51 -16
  71. umap/static/umap/locale/bg.js +51 -16
  72. umap/static/umap/locale/bg.json +51 -16
  73. umap/static/umap/locale/br.js +55 -20
  74. umap/static/umap/locale/br.json +55 -20
  75. umap/static/umap/locale/ca.js +51 -16
  76. umap/static/umap/locale/ca.json +51 -16
  77. umap/static/umap/locale/cs_CZ.js +93 -58
  78. umap/static/umap/locale/cs_CZ.json +93 -58
  79. umap/static/umap/locale/da.js +51 -16
  80. umap/static/umap/locale/da.json +51 -16
  81. umap/static/umap/locale/de.js +56 -21
  82. umap/static/umap/locale/de.json +56 -21
  83. umap/static/umap/locale/el.js +51 -16
  84. umap/static/umap/locale/el.json +51 -16
  85. umap/static/umap/locale/en.js +52 -16
  86. umap/static/umap/locale/en.json +52 -16
  87. umap/static/umap/locale/en_US.json +51 -16
  88. umap/static/umap/locale/es.js +51 -16
  89. umap/static/umap/locale/es.json +51 -16
  90. umap/static/umap/locale/et.js +51 -16
  91. umap/static/umap/locale/et.json +51 -16
  92. umap/static/umap/locale/eu.js +51 -16
  93. umap/static/umap/locale/eu.json +51 -16
  94. umap/static/umap/locale/fa_IR.js +51 -16
  95. umap/static/umap/locale/fa_IR.json +51 -16
  96. umap/static/umap/locale/fi.js +51 -16
  97. umap/static/umap/locale/fi.json +51 -16
  98. umap/static/umap/locale/fr.js +61 -25
  99. umap/static/umap/locale/fr.json +61 -25
  100. umap/static/umap/locale/gl.js +51 -16
  101. umap/static/umap/locale/gl.json +51 -16
  102. umap/static/umap/locale/he.js +51 -16
  103. umap/static/umap/locale/he.json +51 -16
  104. umap/static/umap/locale/hr.js +51 -16
  105. umap/static/umap/locale/hr.json +51 -16
  106. umap/static/umap/locale/hu.js +51 -16
  107. umap/static/umap/locale/hu.json +51 -16
  108. umap/static/umap/locale/id.js +51 -16
  109. umap/static/umap/locale/id.json +51 -16
  110. umap/static/umap/locale/is.js +51 -16
  111. umap/static/umap/locale/is.json +51 -16
  112. umap/static/umap/locale/it.js +51 -16
  113. umap/static/umap/locale/it.json +51 -16
  114. umap/static/umap/locale/ja.js +51 -16
  115. umap/static/umap/locale/ja.json +51 -16
  116. umap/static/umap/locale/ko.js +51 -16
  117. umap/static/umap/locale/ko.json +51 -16
  118. umap/static/umap/locale/lt.js +51 -16
  119. umap/static/umap/locale/lt.json +51 -16
  120. umap/static/umap/locale/ms.js +51 -16
  121. umap/static/umap/locale/ms.json +51 -16
  122. umap/static/umap/locale/nl.js +51 -16
  123. umap/static/umap/locale/nl.json +51 -16
  124. umap/static/umap/locale/no.js +51 -16
  125. umap/static/umap/locale/no.json +51 -16
  126. umap/static/umap/locale/pl.js +93 -58
  127. umap/static/umap/locale/pl.json +93 -58
  128. umap/static/umap/locale/pl_PL.json +51 -16
  129. umap/static/umap/locale/pt.js +215 -180
  130. umap/static/umap/locale/pt.json +215 -180
  131. umap/static/umap/locale/pt_BR.js +51 -16
  132. umap/static/umap/locale/pt_BR.json +51 -16
  133. umap/static/umap/locale/pt_PT.js +51 -16
  134. umap/static/umap/locale/pt_PT.json +51 -16
  135. umap/static/umap/locale/ro.js +51 -16
  136. umap/static/umap/locale/ro.json +51 -16
  137. umap/static/umap/locale/ru.js +51 -16
  138. umap/static/umap/locale/ru.json +51 -16
  139. umap/static/umap/locale/si.js +51 -16
  140. umap/static/umap/locale/si.json +51 -16
  141. umap/static/umap/locale/sk_SK.js +51 -16
  142. umap/static/umap/locale/sk_SK.json +51 -16
  143. umap/static/umap/locale/sl.js +51 -16
  144. umap/static/umap/locale/sl.json +51 -16
  145. umap/static/umap/locale/sr.js +51 -16
  146. umap/static/umap/locale/sr.json +51 -16
  147. umap/static/umap/locale/sv.js +51 -16
  148. umap/static/umap/locale/sv.json +51 -16
  149. umap/static/umap/locale/th_TH.js +51 -16
  150. umap/static/umap/locale/th_TH.json +51 -16
  151. umap/static/umap/locale/tr.js +51 -16
  152. umap/static/umap/locale/tr.json +51 -16
  153. umap/static/umap/locale/uk_UA.js +51 -16
  154. umap/static/umap/locale/uk_UA.json +51 -16
  155. umap/static/umap/locale/vi.js +51 -16
  156. umap/static/umap/locale/vi.json +51 -16
  157. umap/static/umap/locale/vi_VN.json +51 -16
  158. umap/static/umap/locale/zh.js +51 -16
  159. umap/static/umap/locale/zh.json +51 -16
  160. umap/static/umap/locale/zh_CN.json +51 -16
  161. umap/static/umap/locale/zh_TW.Big5.json +51 -16
  162. umap/static/umap/locale/zh_TW.js +51 -16
  163. umap/static/umap/locale/zh_TW.json +51 -16
  164. umap/static/umap/map.css +40 -53
  165. umap/static/umap/unittests/sync.js +105 -0
  166. umap/static/umap/unittests/utils.js +78 -36
  167. umap/static/umap/vars.css +19 -1
  168. umap/static/umap/vendors/dompurify/purify.es.js +50 -15
  169. umap/static/umap/vendors/dompurify/purify.es.mjs.map +1 -1
  170. umap/static/umap/vendors/formbuilder/Leaflet.FormBuilder.js +2 -2
  171. umap/templates/umap/components/alerts/alert.html +89 -0
  172. umap/templates/umap/content.html +4 -3
  173. umap/templates/umap/css.html +4 -0
  174. umap/templates/umap/home.html +3 -0
  175. umap/templates/umap/js.html +0 -3
  176. umap/templates/umap/map_init.html +2 -8
  177. umap/templates/umap/messages.html +9 -11
  178. umap/templates/umap/search.html +3 -0
  179. umap/tests/base.py +3 -0
  180. umap/tests/integration/conftest.py +30 -0
  181. umap/tests/integration/test_anonymous_owned_map.py +8 -13
  182. umap/tests/integration/test_browser.py +81 -6
  183. umap/tests/integration/test_caption.py +27 -0
  184. umap/tests/integration/test_conditional_rules.py +201 -0
  185. umap/tests/integration/test_dashboard.py +1 -1
  186. umap/tests/integration/test_datalayer.py +2 -3
  187. umap/tests/integration/test_edit_datalayer.py +32 -3
  188. umap/tests/integration/test_edit_map.py +1 -1
  189. umap/tests/integration/test_facets_browser.py +7 -4
  190. umap/tests/integration/test_import.py +185 -49
  191. umap/tests/integration/test_map.py +31 -17
  192. umap/tests/integration/{test_collaborative_editing.py → test_optimistic_merge.py} +7 -7
  193. umap/tests/integration/test_owned_map.py +1 -1
  194. umap/tests/integration/test_picto.py +2 -2
  195. umap/tests/integration/test_statics.py +1 -1
  196. umap/tests/integration/test_view_marker.py +19 -2
  197. umap/tests/integration/test_websocket_sync.py +283 -0
  198. umap/tests/settings.py +5 -0
  199. umap/tests/test_datalayer_views.py +0 -1
  200. umap/tests/test_views.py +53 -0
  201. umap/urls.py +5 -0
  202. umap/views.py +40 -11
  203. umap/websocket_server.py +92 -0
  204. {umap_project-2.3.0.dist-info → umap_project-2.4.0.dist-info}/METADATA +13 -11
  205. {umap_project-2.3.0.dist-info → umap_project-2.4.0.dist-info}/RECORD +208 -172
  206. umap/static/umap/js/umap.autocomplete.js +0 -341
  207. umap/static/umap/js/umap.importer.js +0 -187
  208. umap/static/umap/js/umap.ui.js +0 -190
  209. {umap_project-2.3.0.dist-info → umap_project-2.4.0.dist-info}/WHEEL +0 -0
  210. {umap_project-2.3.0.dist-info → umap_project-2.4.0.dist-info}/entry_points.txt +0 -0
  211. {umap_project-2.3.0.dist-info → umap_project-2.4.0.dist-info}/licenses/LICENSE +0 -0
@@ -60,6 +60,9 @@ U.Layer.Cluster = L.MarkerClusterGroup.extend({
60
60
 
61
61
  initialize: function (datalayer) {
62
62
  this.datalayer = datalayer
63
+ if (!U.Utils.isObject(this.datalayer.options.cluster)) {
64
+ this.datalayer.options.cluster = {}
65
+ }
63
66
  const options = {
64
67
  polygonOptions: {
65
68
  color: this.datalayer.getColor(),
@@ -97,9 +100,6 @@ U.Layer.Cluster = L.MarkerClusterGroup.extend({
97
100
  },
98
101
 
99
102
  getEditableOptions: function () {
100
- if (!U.Utils.isObject(this.datalayer.options.cluster)) {
101
- this.datalayer.options.cluster = {}
102
- }
103
103
  return [
104
104
  [
105
105
  'options.cluster.radius',
@@ -179,12 +179,18 @@ U.Layer.Choropleth = L.FeatureGroup.extend({
179
179
  return +feature.properties[key] // TODO: should we catch values non castable to int ?
180
180
  },
181
181
 
182
- computeBreaks: function () {
182
+ getValues: function () {
183
183
  const values = []
184
184
  this.datalayer.eachLayer((layer) => {
185
185
  let value = this._getValue(layer)
186
186
  if (!isNaN(value)) values.push(value)
187
187
  })
188
+ return values
189
+ },
190
+
191
+ computeBreaks: function () {
192
+ const values = this.getValues()
193
+
188
194
  if (!values.length) {
189
195
  this.options.breaks = []
190
196
  this.options.colors = []
@@ -351,6 +357,9 @@ U.Layer.Heat = L.HeatLayer.extend({
351
357
  initialize: function (datalayer) {
352
358
  this.datalayer = datalayer
353
359
  L.HeatLayer.prototype.initialize.call(this, [], this.datalayer.options.heat)
360
+ if (!U.Utils.isObject(this.datalayer.options.heat)) {
361
+ this.datalayer.options.heat = {}
362
+ }
354
363
  },
355
364
 
356
365
  addLayer: function (layer) {
@@ -383,9 +392,6 @@ U.Layer.Heat = L.HeatLayer.extend({
383
392
  },
384
393
 
385
394
  getEditableOptions: function () {
386
- if (!U.Utils.isObject(this.datalayer.options.heat)) {
387
- this.datalayer.options.heat = {}
388
- }
389
395
  return [
390
396
  [
391
397
  'options.heat.radius',
@@ -517,6 +523,7 @@ U.DataLayer = L.Evented.extend({
517
523
 
518
524
  initialize: function (map, data) {
519
525
  this.map = map
526
+ this.sync = map.sync_engine.proxy(this)
520
527
  this._index = Array()
521
528
  this._layers = {}
522
529
  this._geojson = null
@@ -570,6 +577,10 @@ U.DataLayer = L.Evented.extend({
570
577
  }
571
578
  this.setUmapId(data.id)
572
579
  this.setOptions(data)
580
+
581
+ if (!U.Utils.isObject(this.options.remoteData)) {
582
+ this.options.remoteData = {}
583
+ }
573
584
  // Retrocompat
574
585
  if (this.options.remoteData && this.options.remoteData.from) {
575
586
  this.options.fromZoom = this.options.remoteData.from
@@ -597,6 +608,15 @@ U.DataLayer = L.Evented.extend({
597
608
  this.on('datachanged', this.map.onDataLayersChanged, this.map)
598
609
  },
599
610
 
611
+ getSyncMetadata: function () {
612
+ return {
613
+ subject: 'datalayer',
614
+ metadata: {
615
+ id: this.umap_id || null,
616
+ },
617
+ }
618
+ },
619
+
600
620
  render: function (fields, builder) {
601
621
  let impacts = U.Utils.getImpactsFromSchema(fields)
602
622
 
@@ -613,6 +633,7 @@ U.DataLayer = L.Evented.extend({
613
633
  fields.forEach((field) => {
614
634
  this.layer.onEdit(field, builder)
615
635
  })
636
+ this.redraw()
616
637
  this.show()
617
638
  break
618
639
  case 'remote-data':
@@ -863,10 +884,10 @@ U.DataLayer = L.Evented.extend({
863
884
  },
864
885
 
865
886
  isRemoteLayer: function () {
866
- return !!(
887
+ return Boolean(
867
888
  this.options.remoteData &&
868
- this.options.remoteData.url &&
869
- this.options.remoteData.format
889
+ this.options.remoteData.url &&
890
+ this.options.remoteData.format
870
891
  )
871
892
  },
872
893
 
@@ -933,7 +954,14 @@ U.DataLayer = L.Evented.extend({
933
954
  },
934
955
 
935
956
  rawToGeoJSON: function (c, type, callback) {
936
- const toDom = (x) => new DOMParser().parseFromString(x, 'text/xml')
957
+ const toDom = (x) => {
958
+ const doc = new DOMParser().parseFromString(x, 'text/xml')
959
+ const errorNode = doc.querySelector('parsererror')
960
+ if (errorNode) {
961
+ U.Alert.error(L._('Cannot parse data'))
962
+ }
963
+ return doc
964
+ }
937
965
 
938
966
  // TODO add a duck typing guessType
939
967
  if (type === 'csv') {
@@ -965,7 +993,7 @@ U.DataLayer = L.Evented.extend({
965
993
  message: err[0].message,
966
994
  })
967
995
  }
968
- this.map.ui.alert({ content: message, level: 'error', duration: 10000 })
996
+ U.Alert.error(message, 10000)
969
997
  console.error(err)
970
998
  }
971
999
  if (result && result.features.length) {
@@ -992,33 +1020,67 @@ U.DataLayer = L.Evented.extend({
992
1020
  const gj = JSON.parse(c)
993
1021
  callback(gj)
994
1022
  } catch (err) {
995
- this.map.ui.alert({ content: `Invalid JSON file: ${err}` })
1023
+ U.Alert.error(`Invalid JSON file: ${err}`)
996
1024
  return
997
1025
  }
998
1026
  }
999
1027
  },
1000
1028
 
1029
+ // The choice of the name is not ours, because it is required by Leaflet.
1030
+ // It is misleading, as the returned objects are uMap objects, and not
1031
+ // GeoJSON features.
1001
1032
  geojsonToFeatures: function (geojson) {
1002
1033
  if (!geojson) return
1003
1034
  const features = geojson instanceof Array ? geojson : geojson.features
1004
1035
  let i
1005
1036
  let len
1006
- let latlng
1007
- let latlngs
1008
1037
 
1009
1038
  if (features) {
1010
1039
  U.Utils.sortFeatures(features, this.map.getOption('sortKey'), L.lang)
1011
1040
  for (i = 0, len = features.length; i < len; i++) {
1012
1041
  this.geojsonToFeatures(features[i])
1013
1042
  }
1014
- return this
1043
+ return this // Why returning "this" ?
1015
1044
  }
1016
1045
 
1017
1046
  const geometry = geojson.type === 'Feature' ? geojson.geometry : geojson
1047
+
1048
+ let feature = this.geoJSONToLeaflet({ geometry, geojson })
1049
+ if (feature) {
1050
+ this.addLayer(feature)
1051
+ feature.onCommit()
1052
+ return feature
1053
+ }
1054
+ },
1055
+
1056
+ /**
1057
+ * Create or update Leaflet features from GeoJSON geometries.
1058
+ *
1059
+ * If no `feature` is provided, a new feature will be created.
1060
+ * If `feature` is provided, it will be updated with the passed geometry.
1061
+ *
1062
+ * GeoJSON and Leaflet use incompatible formats to encode coordinates.
1063
+ * This method takes care of the convertion.
1064
+ *
1065
+ * @param geometry GeoJSON geometry field
1066
+ * @param geojson Enclosing GeoJSON. If none is provided, a new one will
1067
+ * be created
1068
+ * @param id Id of the feature
1069
+ * @param feature Leaflet feature that should be updated with the new geometry
1070
+ * @returns Leaflet feature.
1071
+ */
1072
+ geoJSONToLeaflet: function ({
1073
+ geometry,
1074
+ geojson = null,
1075
+ id = null,
1076
+ feature = null,
1077
+ } = {}) {
1018
1078
  if (!geometry) return // null geometry is valid geojson.
1019
1079
  const coords = geometry.coordinates
1020
- let layer
1021
- let tmp
1080
+ let latlng, latlngs
1081
+
1082
+ // Create a default geojson if none is provided
1083
+ if (geojson === undefined) geojson = { type: 'Feature', geometry: geometry }
1022
1084
 
1023
1085
  switch (geometry.type) {
1024
1086
  case 'Point':
@@ -1028,8 +1090,11 @@ U.DataLayer = L.Evented.extend({
1028
1090
  console.error('Invalid latlng object from', coords)
1029
1091
  break
1030
1092
  }
1031
- layer = this._pointToLayer(geojson, latlng)
1032
- break
1093
+ if (feature) {
1094
+ feature.setLatLng(latlng)
1095
+ return feature
1096
+ }
1097
+ return this._pointToLayer(geojson, latlng, id)
1033
1098
 
1034
1099
  case 'MultiLineString':
1035
1100
  case 'LineString':
@@ -1038,49 +1103,55 @@ U.DataLayer = L.Evented.extend({
1038
1103
  geometry.type === 'LineString' ? 0 : 1
1039
1104
  )
1040
1105
  if (!latlngs.length) break
1041
- layer = this._lineToLayer(geojson, latlngs)
1042
- break
1106
+ if (feature) {
1107
+ feature.setLatLngs(latlngs)
1108
+ return feature
1109
+ }
1110
+ return this._lineToLayer(geojson, latlngs, id)
1043
1111
 
1044
1112
  case 'MultiPolygon':
1045
1113
  case 'Polygon':
1046
1114
  latlngs = L.GeoJSON.coordsToLatLngs(coords, geometry.type === 'Polygon' ? 1 : 2)
1047
- layer = this._polygonToLayer(geojson, latlngs)
1048
- break
1115
+ if (feature) {
1116
+ feature.setLatLngs(latlngs)
1117
+ return feature
1118
+ }
1119
+ return this._polygonToLayer(geojson, latlngs, id)
1049
1120
  case 'GeometryCollection':
1050
1121
  return this.geojsonToFeatures(geometry.geometries)
1051
1122
 
1052
1123
  default:
1053
- this.map.ui.alert({
1054
- content: L._('Skipping unknown geometry.type: {type}', {
1124
+ U.Alert.error(
1125
+ L._('Skipping unknown geometry.type: {type}', {
1055
1126
  type: geometry.type || 'undefined',
1056
- }),
1057
- level: 'error',
1058
- })
1059
- }
1060
- if (layer) {
1061
- this.addLayer(layer)
1062
- return layer
1127
+ })
1128
+ )
1063
1129
  }
1064
1130
  },
1065
1131
 
1066
- _pointToLayer: function (geojson, latlng) {
1067
- return new U.Marker(this.map, latlng, { geojson: geojson, datalayer: this })
1132
+ _pointToLayer: function (geojson, latlng, id) {
1133
+ return new U.Marker(this.map, latlng, { geojson: geojson, datalayer: this }, id)
1068
1134
  },
1069
1135
 
1070
- _lineToLayer: function (geojson, latlngs) {
1071
- return new U.Polyline(this.map, latlngs, {
1072
- geojson: geojson,
1073
- datalayer: this,
1074
- color: null,
1075
- })
1136
+ _lineToLayer: function (geojson, latlngs, id) {
1137
+ return new U.Polyline(
1138
+ this.map,
1139
+ latlngs,
1140
+ {
1141
+ geojson: geojson,
1142
+ datalayer: this,
1143
+ color: null,
1144
+ },
1145
+ id
1146
+ )
1076
1147
  },
1077
1148
 
1078
- _polygonToLayer: function (geojson, latlngs) {
1149
+ _polygonToLayer: function (geojson, latlngs, id) {
1079
1150
  // Ensure no empty hole
1080
1151
  // for (let i = latlngs.length - 1; i > 0; i--) {
1081
1152
  // if (!latlngs.slice()[i].length) latlngs.splice(i, 1);
1082
1153
  // }
1083
- return new U.Polygon(this.map, latlngs, { geojson: geojson, datalayer: this })
1154
+ return new U.Polygon(this.map, latlngs, { geojson: geojson, datalayer: this }, id)
1084
1155
  },
1085
1156
 
1086
1157
  importRaw: function (raw, type) {
@@ -1301,9 +1372,12 @@ U.DataLayer = L.Evented.extend({
1301
1372
  )
1302
1373
  popupFieldset.appendChild(builder.build())
1303
1374
 
1375
+ // XXX I'm not sure **why** this is needed (as it's set during `this.initialize`)
1376
+ // but apparently it's needed.
1304
1377
  if (!U.Utils.isObject(this.options.remoteData)) {
1305
1378
  this.options.remoteData = {}
1306
1379
  }
1380
+
1307
1381
  const remoteDataFields = [
1308
1382
  [
1309
1383
  'options.remoteData.url',
@@ -1333,10 +1407,7 @@ U.DataLayer = L.Evented.extend({
1333
1407
  helpEntries: 'proxyRemoteData',
1334
1408
  },
1335
1409
  ])
1336
- remoteDataFields.push([
1337
- 'options.remoteData.ttl',
1338
- { handler: 'ProxyTTLSelect', label: L._('Cache proxied request') },
1339
- ])
1410
+ remoteDataFields.push('options.remoteData.ttl')
1340
1411
  }
1341
1412
 
1342
1413
  const remoteDataContainer = L.DomUtil.createFieldset(container, L._('Remote data'))
@@ -1392,17 +1463,19 @@ U.DataLayer = L.Evented.extend({
1392
1463
  '_blank'
1393
1464
  )
1394
1465
  }
1395
- const button = L.DomUtil.create('li', '')
1396
- L.DomUtil.create('i', 'icon icon-16 icon-back', button)
1397
- button.title = L._('Back to layers')
1466
+ const backButton = L.DomUtil.createButtonIcon(
1467
+ undefined,
1468
+ 'icon-back',
1469
+ L._('Back to layers')
1470
+ )
1398
1471
  // Fixme: remove me when this is merged and released
1399
1472
  // https://github.com/Leaflet/Leaflet/pull/9052
1400
- L.DomEvent.disableClickPropagation(button)
1401
- L.DomEvent.on(button, 'click', this.map.editDatalayers, this.map)
1473
+ L.DomEvent.disableClickPropagation(backButton)
1474
+ L.DomEvent.on(backButton, 'click', this.map.editDatalayers, this.map)
1402
1475
 
1403
1476
  this.map.editPanel.open({
1404
1477
  content: container,
1405
- actions: [button],
1478
+ actions: [backButton],
1406
1479
  })
1407
1480
  },
1408
1481
 
@@ -1420,7 +1493,7 @@ U.DataLayer = L.Evented.extend({
1420
1493
  } else if (this.layer && this.layer.defaults && this.layer.defaults[option]) {
1421
1494
  return this.layer.defaults[option]
1422
1495
  } else {
1423
- return this.map.getOption(option)
1496
+ return this.map.getOption(option, feature)
1424
1497
  }
1425
1498
  },
1426
1499
 
@@ -1528,7 +1601,7 @@ U.DataLayer = L.Evented.extend({
1528
1601
  },
1529
1602
 
1530
1603
  isVisible: function () {
1531
- return this.layer && this.map.hasLayer(this.layer)
1604
+ return Boolean(this.layer && this.map.hasLayer(this.layer))
1532
1605
  },
1533
1606
 
1534
1607
  getFeatureByIndex: function (index) {
@@ -1537,6 +1610,12 @@ U.DataLayer = L.Evented.extend({
1537
1610
  return this._layers[id]
1538
1611
  },
1539
1612
 
1613
+ // TODO Add an index
1614
+ // For now, iterate on all the features.
1615
+ getFeatureById: function (id) {
1616
+ return Object.values(this._layers).find((feature) => feature.id === id)
1617
+ },
1618
+
1540
1619
  getNextFeature: function (feature) {
1541
1620
  const id = this._index.indexOf(L.stamp(feature))
1542
1621
  const nextId = this._index[id + 1]
@@ -1625,28 +1704,15 @@ U.DataLayer = L.Evented.extend({
1625
1704
  const [data, response, error] = await this.map.server.post(url, headers, formData)
1626
1705
  if (error) {
1627
1706
  if (response && response.status === 412) {
1628
- const msg = L._(
1629
- 'Woops! Someone else seems to have edited the data. You can save anyway, but this will erase the changes made by others.'
1707
+ U.AlertConflict.error(
1708
+ L._(
1709
+ 'Whoops! Other contributor(s) changed some of the same map elements as you. ' +
1710
+ 'This situation is tricky, you have to choose carefully which version is pertinent.'
1711
+ ),
1712
+ async () => {
1713
+ await this._trySave(url, {}, formData)
1714
+ }
1630
1715
  )
1631
- const actions = [
1632
- {
1633
- label: L._('Save anyway'),
1634
- callback: async () => {
1635
- // Save again,
1636
- // but do not pass the reference version this time
1637
- await this._trySave(url, {}, formData)
1638
- },
1639
- },
1640
- {
1641
- label: L._('Cancel'),
1642
- },
1643
- ]
1644
- this.map.ui.alert({
1645
- content: msg,
1646
- level: 'error',
1647
- duration: 100000,
1648
- actions: actions,
1649
- })
1650
1716
  }
1651
1717
  } else {
1652
1718
  // Response contains geojson only if save has conflicted and conflicts have
@@ -1657,6 +1723,8 @@ U.DataLayer = L.Evented.extend({
1657
1723
  delete data.geojson
1658
1724
  }
1659
1725
  this._reference_version = response.headers.get('X-Datalayer-Version')
1726
+ this.sync.update('_reference_version', this._reference_version)
1727
+
1660
1728
  this.setUmapId(data.id)
1661
1729
  this.updateOptions(data)
1662
1730
  this.backupOptions()
@@ -1689,6 +1757,16 @@ U.DataLayer = L.Evented.extend({
1689
1757
  const editor = new U.TableEditor(this)
1690
1758
  editor.edit()
1691
1759
  },
1760
+
1761
+ getFilterKeys: function () {
1762
+ // This keys will be used to filter feature from the browser text input.
1763
+ // By default, it will we use the "name" property, which is also the one used as label in the features list.
1764
+ // When map owner has configured another label or sort key, we try to be smart and search in the same keys.
1765
+ if (this.map.options.filterKey) return this.map.options.filterKey
1766
+ else if (this.options.labelKey) return this.options.labelKey
1767
+ else if (this.map.options.sortKey) return this.map.options.sortKey
1768
+ else return 'name'
1769
+ },
1692
1770
  })
1693
1771
 
1694
1772
  L.TileLayer.include({
@@ -52,11 +52,9 @@ U.MapPermissions = L.Class.extend({
52
52
 
53
53
  edit: function () {
54
54
  if (this.map.options.editMode !== 'advanced') return
55
- if (!this.map.options.umap_id)
56
- return this.map.ui.alert({
57
- content: L._('Please save the map first'),
58
- level: 'info',
59
- })
55
+ if (!this.map.options.umap_id) {
56
+ return U.Alert.info(L._('Please save the map first'))
57
+ }
60
58
  const container = L.DomUtil.create('div', 'permissions-panel')
61
59
  const fields = []
62
60
  L.DomUtil.createTitle(container, L._('Update permissions'), 'icon-key')
@@ -109,6 +107,7 @@ U.MapPermissions = L.Class.extend({
109
107
  { handler: 'ManageEditors', label: L._("Map's editors") },
110
108
  ])
111
109
  }
110
+
112
111
  const builder = new U.FormBuilder(this, fields)
113
112
  const form = builder.build()
114
113
  container.appendChild(form)
@@ -139,10 +138,7 @@ U.MapPermissions = L.Class.extend({
139
138
  const [data, response, error] = await this.map.server.post(this.getAttachUrl())
140
139
  if (!error) {
141
140
  this.options.owner = this.map.options.user
142
- this.map.ui.alert({
143
- content: L._('Map has been attached to your account'),
144
- level: 'info',
145
- })
141
+ U.Alert.success(L._('Map has been attached to your account'))
146
142
  this.map.editPanel.close()
147
143
  }
148
144
  },
@@ -55,6 +55,7 @@ U.Popup.Panel = U.Popup.extend({
55
55
  },
56
56
 
57
57
  onAdd: function (map) {
58
+ map.panel.setDefaultMode('expanded')
58
59
  map.panel.open({
59
60
  content: this._content,
60
61
  actions: [U.Browser.backButton(map)],
@@ -106,7 +107,7 @@ U.PopupTemplate.Default = L.Class.extend({
106
107
  renderBody: function () {
107
108
  const template = this.feature.getOption('popupContentTemplate')
108
109
  const target = this.feature.getOption('outlinkTarget')
109
- const container = L.DomUtil.create('div', 'umap-popup-container')
110
+ const container = L.DomUtil.create('div', 'umap-popup-container text')
110
111
  let content = ''
111
112
  let properties
112
113
  let center
@@ -83,10 +83,7 @@ U.TableEditor = L.Class.extend({
83
83
 
84
84
  validateName: function (name) {
85
85
  if (name.indexOf('.') !== -1) {
86
- this.datalayer.map.ui.alert({
87
- content: L._('Invalide property name: {name}', { name: name }),
88
- level: 'error',
89
- })
86
+ U.Alert.error(L._('Invalide property name: {name}', { name: name }))
90
87
  return false
91
88
  }
92
89
  return true
@@ -98,10 +95,13 @@ U.TableEditor = L.Class.extend({
98
95
  this.renderHeaders()
99
96
  this.body.innerHTML = ''
100
97
  this.datalayer.eachLayer(this.renderRow, this)
101
- const addButton = L.DomUtil.create('li', 'add-property')
102
- L.DomUtil.createIcon(addButton, 'icon-add')
103
- const label = L.DomUtil.create('span', '', addButton)
104
- label.textContent = label.title = L._('Add a new property')
98
+ const addButton = L.DomUtil.createButton(
99
+ 'flat',
100
+ undefined,
101
+ L._('Add a new property')
102
+ )
103
+ const iconElement = L.DomUtil.createIcon(addButton, 'icon-add')
104
+ addButton.insertBefore(iconElement, addButton.firstChild)
105
105
  const addProperty = function () {
106
106
  const newName = prompt(L._('Please enter the name of the property'))
107
107
  if (!newName || !this.validateName(newName)) return
@@ -48,16 +48,12 @@ const locale = {
48
48
  "by": "በ",
49
49
  "Cache proxied request": "Cache proxied request",
50
50
  "Cancel edits": "እርማቶችን ሰርዝ",
51
- "Cancel": "አቁም/ሰርዝ",
52
51
  "Caption": "ካፕሽን",
53
52
  "Center map on your location": "መገኛዎን የሚያሳየውን ካርታ ወደ መሀል ያድርጉ",
54
53
  "Change map background": "የካርታውን የጀርባ ገፅታ ይቀይሩ",
55
54
  "Change tilelayers": "የታይልሌየሩን",
56
55
  "Change": "Change",
57
- "Choose a preset": "ፕሪሴት ምረጥ",
58
56
  "Choose the data format": "የረጃውን ፎርማት ቀይር",
59
- "Choose the format of the data to import": "ሊያመጡ የፈለጉትን የመረጃ ፎርማት ይምረጡ",
60
- "Choose the layer to import in": "የሚያስገቡበት ሌየር ይምረጡ",
61
57
  "Choropleth breakpoints": "Choropleth breakpoints",
62
58
  "Choropleth classes": "Choropleth classes",
63
59
  "Choropleth color palette": "Choropleth color palette",
@@ -87,7 +83,6 @@ const locale = {
87
83
  "Congratulations, your map has been created!": "Congratulations, your map has been created!",
88
84
  "Continue line": "መስመሩን ቀጥል",
89
85
  "Coordinates": "ኮርዲኔቶች",
90
- "Copy link": "Copy link",
91
86
  "copy": "copy",
92
87
  "Credits": "ክሬዲቶች",
93
88
  "Current map view": "Current map view",
@@ -152,7 +147,6 @@ const locale = {
152
147
  "Edit the title of the map": "Edit the title of the map",
153
148
  "Edit this feature": "ይህንን ፊቸር አርም",
154
149
  "Edit": "አርም",
155
- "Email": "Email",
156
150
  "Embed and link options": "Embed and link options",
157
151
  "Embed the map": "ካርታውን አካትት",
158
152
  "Emoji & Character": "Emoji & Character",
@@ -200,7 +194,6 @@ const locale = {
200
194
  "Image: {{http://image.url.com}}": "ምስል፡{{http://image.url.com}}",
201
195
  "Import data": "መረጃ አምጣ",
202
196
  "Import in a new layer": "አዲስ ሌየር አምጣ",
203
- "Import": "አምጣ",
204
197
  "Imports all umap data, including layers and settings.": "ሌየር እና ሁኔታዎቹን ጨምሮ ሁሉንም የዩማፕ መረጃ ያመጣል",
205
198
  "Include full screen link?": "ሙሉ ስክሪን ሊክን ያካትት?",
206
199
  "Inherit": "ውረስ",
@@ -311,7 +304,6 @@ const locale = {
311
304
  "Rename this property on all the features": "በሁሉም ፊቸሮች ይህንን ያባህርይ መጠሪያ ያሻሽሉ",
312
305
  "Replace layer content": "Replace layer content",
313
306
  "Restore this version": "ይህንን እትም መልስ",
314
- "Save anyway": "ለማንኛውም አስቀምጥ/አድን",
315
307
  "Save current edits": "የአሁኑን እርማቶች አስቀምጥ/አድን",
316
308
  "Save map": "Save map",
317
309
  "Save this center and zoom": "ይህንን ዙም እና መሀከል አስቀምጥ/አድን",
@@ -320,12 +312,9 @@ const locale = {
320
312
  "Saved center and zoom": "Saved center and zoom",
321
313
  "Search location": "Search location",
322
314
  "Search": "Search",
323
- "Secret edit link copied to clipboard!": "Secret edit link copied to clipboard!",
324
315
  "Secret edit link:": "Secret edit link:",
325
- "See layers": "See layers",
326
316
  "See full screen": "ሙሉውን ስክሪን ተመልከት",
327
317
  "See on OpenStreetMap": "See on OpenStreetMap",
328
- "Send me the link": "Send me the link",
329
318
  "Set it to false to hide this layer from the slideshow, the data browser, the popup navigation…": "Set it to false to hide this layer from the slideshow, the data browser, the popup navigation…",
330
319
  "settings": "settings",
331
320
  "Shape properties": "Shape properties",
@@ -398,9 +387,7 @@ const locale = {
398
387
  "Will be displayed in the bottom right corner of the map": "ከታች በቀኝ ኮርነሩ ላይ ይታያል",
399
388
  "Will be permanently visible in the bottom left corner of the map": "Will be permanently visible in the bottom left corner of the map",
400
389
  "Will be visible in the caption of the map": "በካርታው ካፕሽን ላይ እንዲታይ ይደረጋል",
401
- "Woops! Someone else seems to have edited the data. You can save anyway, but this will erase the changes made by others.": "ውይ! ሌላ ሰው መረጃውን ሳያርመው አይቀርም። ለማንኛውም ማዳን/ማስቀመጥ ይቻላል፣ ነገር ግን የሌሎች እርማቶች ይሰረዛሉ",
402
390
  "yes": "አዎን",
403
- "Your map has been created! As you are not logged in, here is your secret link to edit the map, please keep it safe:": "Your map has been created! As you are not logged in, here is your secret link to edit the map, please keep it safe:",
404
391
  "Zoom in": "አጉላ",
405
392
  "Zoom level for automatic zooms": "አውቶማቲክ ዙሞች የሚጎሉበት መጠን",
406
393
  "Zoom out": "አርቅ",
@@ -444,9 +431,57 @@ const locale = {
444
431
  "Filter data": "Filter data",
445
432
  "Search map features…": "Search map features…",
446
433
  "Reset all": "Reset all",
447
- "Browser in data mode": "Browser in data mode",
448
- "Browser in layers mode": "Browser in layers mode",
449
- "Browser in filters mode": "Browser in filters mode"
434
+ "Open browser": "Open browser",
435
+ "Open caption": "Open caption",
436
+ "Your map has been created with an anonymous account!": "Your map has been created with an anonymous account!",
437
+ "Real-time collaboration": "Real-time collaboration",
438
+ "Cannot parse data": "Cannot parse data",
439
+ "Start typing...": "Start typing...",
440
+ "No result": "No result",
441
+ "Data browser": "Data browser",
442
+ "When providing an URL, uMap can copy the remote data in a layer, or add this URL as remote source of the layer. In that case, data will always be fetched from that URL, and thus be up to date, but it will not be possible to edit it inside uMap.": "When providing an URL, uMap can copy the remote data in a layer, or add this URL as remote source of the layer. In that case, data will always be fetched from that URL, and thus be up to date, but it will not be possible to edit it inside uMap.",
443
+ "Overpass supported expressions": "Overpass supported expressions",
444
+ "key (eg. building)": "key (eg. building)",
445
+ "!key (eg. !name)": "!key (eg. !name)",
446
+ "key=value (eg. building=yes)": "key=value (eg. building=yes)",
447
+ "key!=value (eg. building!=yes)": "key!=value (eg. building!=yes)",
448
+ "key~value (eg. name~Grisy)": "key~value (eg. name~Grisy)",
449
+ "key=\"value|value2\" (eg. name=\"Paris|Berlin\")": "key=\"value|value2\" (eg. name=\"Paris|Berlin\")",
450
+ "More info about Overpass syntax": "More info about Overpass syntax",
451
+ "For more complex needs, see": "For more complex needs, see",
452
+ "Choose data": "Choose data",
453
+ "Import helpers:": "Import helpers:",
454
+ "Choose the format": "Choose the format",
455
+ "Choose the layer": "Choose the layer",
456
+ "Layer name": "Layer name",
457
+ "Choose import mode": "Choose import mode",
458
+ "Copy into the layer": "Copy into the layer",
459
+ "Link to the layer as remote data": "Link to the layer as remote data",
460
+ "Condition": "Condition",
461
+ "key=value or key!=value": "key=value or key!=value",
462
+ "Are you sure you want to delete this rule?": "Are you sure you want to delete this rule?",
463
+ "empty rule": "empty rule",
464
+ "Conditional style rules": "Conditional style rules",
465
+ "Add rule": "Add rule",
466
+ "Browser: data": "Browser: data",
467
+ "Browser: layers": "Browser: layers",
468
+ "Browser: filters": "Browser: filters",
469
+ "Enable real-time collaboration": "Enable real-time collaboration",
470
+ "✅ Copied!": "✅ Copied!",
471
+ "Choose a dataset": "Choose a dataset",
472
+ "Choose this dataset": "Choose this dataset",
473
+ "GeoDataMine: thematic data from OpenStreetMap": "GeoDataMine: thematic data from OpenStreetMap",
474
+ "Choose a theme": "Choose a theme",
475
+ "Symplify all geometries to points": "Symplify all geometries to points",
476
+ "Choose this data": "Choose this data",
477
+ "Search admin boundary": "Search admin boundary",
478
+ "Please choose a theme and a boundary first.": "Please choose a theme and a boundary first.",
479
+ "Expression": "Expression",
480
+ "Geometry mode": "Geometry mode",
481
+ "Only geometry centers": "Only geometry centers",
482
+ "Search area": "Search area",
483
+ "Type area name, or let empty to load data in current map view": "Type area name, or let empty to load data in current map view",
484
+ "Please define an expression for the query first": "Please define an expression for the query first"
450
485
  }
451
486
  L.registerLocale("am_ET", locale)
452
487
  L.setLocale("am_ET")