umap-project 2.6.2__py3-none-any.whl → 2.7.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/admin.py +64 -1
  3. umap/asgi.py +15 -0
  4. umap/context_processors.py +1 -0
  5. umap/locale/cs_CZ/LC_MESSAGES/django.mo +0 -0
  6. umap/locale/cs_CZ/LC_MESSAGES/django.po +96 -92
  7. umap/locale/de/LC_MESSAGES/django.mo +0 -0
  8. umap/locale/de/LC_MESSAGES/django.po +19 -18
  9. umap/locale/en/LC_MESSAGES/django.po +47 -43
  10. umap/locale/es/LC_MESSAGES/django.mo +0 -0
  11. umap/locale/es/LC_MESSAGES/django.po +134 -128
  12. umap/locale/fr/LC_MESSAGES/django.mo +0 -0
  13. umap/locale/fr/LC_MESSAGES/django.po +51 -47
  14. umap/locale/pt/LC_MESSAGES/django.mo +0 -0
  15. umap/locale/pt/LC_MESSAGES/django.po +64 -60
  16. umap/management/commands/clean_tilelayer.py +152 -0
  17. umap/management/commands/purge_purgatory.py +28 -0
  18. umap/models.py +27 -2
  19. umap/settings/base.py +3 -1
  20. umap/static/umap/base.css +4 -4
  21. umap/static/umap/css/contextmenu.css +6 -1
  22. umap/static/umap/css/icon.css +7 -2
  23. umap/static/umap/css/importers.css +4 -0
  24. umap/static/umap/img/16-white.svg +9 -2
  25. umap/static/umap/img/16.svg +1 -181
  26. umap/static/umap/img/24-white.svg +1 -0
  27. umap/static/umap/img/24.svg +1 -0
  28. umap/static/umap/img/importers/cadastrefr.svg +23 -0
  29. umap/static/umap/img/source/16-white.svg +10 -3
  30. umap/static/umap/img/source/16.svg +753 -197
  31. umap/static/umap/img/source/24-white.svg +3 -2
  32. umap/static/umap/img/source/24.svg +3 -2
  33. umap/static/umap/js/modules/autocomplete.js +7 -3
  34. umap/static/umap/js/modules/browser.js +55 -2
  35. umap/static/umap/js/modules/caption.js +16 -5
  36. umap/static/umap/js/modules/data/features.js +183 -8
  37. umap/static/umap/js/modules/data/layer.js +57 -40
  38. umap/static/umap/js/modules/formatter.js +3 -2
  39. umap/static/umap/js/modules/global.js +2 -0
  40. umap/static/umap/js/modules/importer.js +3 -0
  41. umap/static/umap/js/modules/importers/cadastrefr.js +62 -0
  42. umap/static/umap/js/modules/importers/communesfr.js +15 -3
  43. umap/static/umap/js/modules/permissions.js +123 -93
  44. umap/static/umap/js/modules/rendering/layers/classified.js +2 -0
  45. umap/static/umap/js/modules/rendering/ui.js +60 -213
  46. umap/static/umap/js/modules/share.js +1 -3
  47. umap/static/umap/js/modules/slideshow.js +1 -1
  48. umap/static/umap/js/modules/sync/engine.js +371 -14
  49. umap/static/umap/js/modules/sync/hlc.js +106 -0
  50. umap/static/umap/js/modules/sync/updaters.js +18 -6
  51. umap/static/umap/js/modules/sync/websocket.js +1 -1
  52. umap/static/umap/js/modules/tableeditor.js +1 -1
  53. umap/static/umap/js/modules/ui/base.js +2 -2
  54. umap/static/umap/js/modules/ui/contextmenu.js +51 -18
  55. umap/static/umap/js/modules/urls.js +5 -1
  56. umap/static/umap/js/modules/utils.js +28 -4
  57. umap/static/umap/js/umap.controls.js +76 -55
  58. umap/static/umap/js/umap.core.js +3 -3
  59. umap/static/umap/js/umap.forms.js +3 -1
  60. umap/static/umap/js/umap.js +115 -124
  61. umap/static/umap/locale/am_ET.js +2 -2
  62. umap/static/umap/locale/am_ET.json +2 -2
  63. umap/static/umap/locale/ar.js +2 -2
  64. umap/static/umap/locale/ar.json +2 -2
  65. umap/static/umap/locale/ast.js +2 -2
  66. umap/static/umap/locale/ast.json +2 -2
  67. umap/static/umap/locale/bg.js +2 -2
  68. umap/static/umap/locale/bg.json +2 -2
  69. umap/static/umap/locale/br.js +13 -4
  70. umap/static/umap/locale/br.json +13 -4
  71. umap/static/umap/locale/ca.js +30 -17
  72. umap/static/umap/locale/ca.json +30 -17
  73. umap/static/umap/locale/cs_CZ.js +89 -80
  74. umap/static/umap/locale/cs_CZ.json +89 -80
  75. umap/static/umap/locale/da.js +2 -2
  76. umap/static/umap/locale/da.json +2 -2
  77. umap/static/umap/locale/de.js +17 -8
  78. umap/static/umap/locale/de.json +17 -8
  79. umap/static/umap/locale/el.js +2 -2
  80. umap/static/umap/locale/el.json +2 -2
  81. umap/static/umap/locale/en.js +15 -4
  82. umap/static/umap/locale/en.json +15 -4
  83. umap/static/umap/locale/en_US.json +2 -2
  84. umap/static/umap/locale/es.js +338 -325
  85. umap/static/umap/locale/es.json +338 -325
  86. umap/static/umap/locale/et.js +2 -2
  87. umap/static/umap/locale/et.json +2 -2
  88. umap/static/umap/locale/eu.js +11 -4
  89. umap/static/umap/locale/eu.json +11 -4
  90. umap/static/umap/locale/fa_IR.js +11 -4
  91. umap/static/umap/locale/fa_IR.json +11 -4
  92. umap/static/umap/locale/fi.js +2 -2
  93. umap/static/umap/locale/fi.json +2 -2
  94. umap/static/umap/locale/fr.js +15 -4
  95. umap/static/umap/locale/fr.json +15 -4
  96. umap/static/umap/locale/gl.js +2 -2
  97. umap/static/umap/locale/gl.json +2 -2
  98. umap/static/umap/locale/he.js +2 -2
  99. umap/static/umap/locale/he.json +2 -2
  100. umap/static/umap/locale/hr.js +2 -2
  101. umap/static/umap/locale/hr.json +2 -2
  102. umap/static/umap/locale/hu.js +12 -5
  103. umap/static/umap/locale/hu.json +12 -5
  104. umap/static/umap/locale/id.js +2 -2
  105. umap/static/umap/locale/id.json +2 -2
  106. umap/static/umap/locale/is.js +2 -2
  107. umap/static/umap/locale/is.json +2 -2
  108. umap/static/umap/locale/it.js +2 -2
  109. umap/static/umap/locale/it.json +2 -2
  110. umap/static/umap/locale/ja.js +2 -2
  111. umap/static/umap/locale/ja.json +2 -2
  112. umap/static/umap/locale/ko.js +2 -2
  113. umap/static/umap/locale/ko.json +2 -2
  114. umap/static/umap/locale/lt.js +2 -2
  115. umap/static/umap/locale/lt.json +2 -2
  116. umap/static/umap/locale/ms.js +2 -2
  117. umap/static/umap/locale/ms.json +2 -2
  118. umap/static/umap/locale/nl.js +2 -2
  119. umap/static/umap/locale/nl.json +2 -2
  120. umap/static/umap/locale/no.js +2 -2
  121. umap/static/umap/locale/no.json +2 -2
  122. umap/static/umap/locale/pl.js +2 -2
  123. umap/static/umap/locale/pl.json +2 -2
  124. umap/static/umap/locale/pl_PL.json +2 -2
  125. umap/static/umap/locale/pt.js +19 -10
  126. umap/static/umap/locale/pt.json +19 -10
  127. umap/static/umap/locale/pt_BR.js +2 -2
  128. umap/static/umap/locale/pt_BR.json +2 -2
  129. umap/static/umap/locale/pt_PT.js +13 -4
  130. umap/static/umap/locale/pt_PT.json +13 -4
  131. umap/static/umap/locale/ro.js +2 -2
  132. umap/static/umap/locale/ro.json +2 -2
  133. umap/static/umap/locale/ru.js +2 -2
  134. umap/static/umap/locale/ru.json +2 -2
  135. umap/static/umap/locale/si.js +2 -2
  136. umap/static/umap/locale/si.json +2 -2
  137. umap/static/umap/locale/sk_SK.js +2 -2
  138. umap/static/umap/locale/sk_SK.json +2 -2
  139. umap/static/umap/locale/sl.js +2 -2
  140. umap/static/umap/locale/sl.json +2 -2
  141. umap/static/umap/locale/sr.js +2 -2
  142. umap/static/umap/locale/sr.json +2 -2
  143. umap/static/umap/locale/sv.js +2 -2
  144. umap/static/umap/locale/sv.json +2 -2
  145. umap/static/umap/locale/th_TH.js +2 -2
  146. umap/static/umap/locale/th_TH.json +2 -2
  147. umap/static/umap/locale/tr.js +2 -2
  148. umap/static/umap/locale/tr.json +2 -2
  149. umap/static/umap/locale/uk_UA.js +2 -2
  150. umap/static/umap/locale/uk_UA.json +2 -2
  151. umap/static/umap/locale/vi.js +2 -2
  152. umap/static/umap/locale/vi.json +2 -2
  153. umap/static/umap/locale/vi_VN.json +2 -2
  154. umap/static/umap/locale/zh.js +2 -2
  155. umap/static/umap/locale/zh.json +2 -2
  156. umap/static/umap/locale/zh_CN.json +2 -2
  157. umap/static/umap/locale/zh_TW.Big5.json +2 -2
  158. umap/static/umap/locale/zh_TW.js +13 -4
  159. umap/static/umap/locale/zh_TW.json +13 -4
  160. umap/static/umap/map.css +44 -29
  161. umap/static/umap/unittests/hlc.js +165 -0
  162. umap/static/umap/unittests/sync.js +321 -15
  163. umap/static/umap/unittests/utils.js +47 -0
  164. umap/static/umap/vars.css +2 -1
  165. umap/static/umap/vendors/colorbrewer/colorbrewer.js +309 -317
  166. umap/static/umap/vendors/dompurify/purify.es.js +15 -16
  167. umap/static/umap/vendors/dompurify/purify.es.mjs.map +1 -1
  168. umap/static/umap/vendors/georsstogeojson/GeoRSSToGeoJSON.js +111 -80
  169. umap/static/umap/vendors/locatecontrol/L.Control.Locate.min.js +2 -2
  170. umap/static/umap/vendors/locatecontrol/L.Control.Locate.min.js.map +1 -1
  171. umap/static/umap/vendors/simple-statistics/simple-statistics.min.js +1 -1
  172. umap/static/umap/vendors/simple-statistics/simple-statistics.min.js.map +1 -1
  173. umap/templates/umap/css.html +0 -2
  174. umap/templates/umap/dashboard_menu.html +4 -2
  175. umap/templates/umap/js.html +0 -5
  176. umap/templates/umap/map_detail.html +2 -2
  177. umap/tests/fixtures/test_upload_data.csv +2 -2
  178. umap/tests/integration/test_anonymous_owned_map.py +1 -0
  179. umap/tests/integration/test_basics.py +1 -1
  180. umap/tests/integration/test_browser.py +69 -7
  181. umap/tests/integration/test_caption.py +3 -3
  182. umap/tests/integration/test_circles_layer.py +12 -0
  183. umap/tests/integration/test_cluster.py +53 -0
  184. umap/tests/integration/test_datalayer.py +2 -1
  185. umap/tests/integration/test_draw_polygon.py +17 -9
  186. umap/tests/integration/test_draw_polyline.py +84 -7
  187. umap/tests/integration/test_edit_datalayer.py +5 -8
  188. umap/tests/integration/test_edit_map.py +2 -2
  189. umap/tests/integration/test_edit_marker.py +1 -1
  190. umap/tests/integration/test_facets_browser.py +3 -3
  191. umap/tests/integration/test_import.py +1 -0
  192. umap/tests/integration/test_map.py +1 -0
  193. umap/tests/integration/test_owned_map.py +1 -1
  194. umap/tests/integration/test_view_marker.py +63 -0
  195. umap/tests/integration/test_view_polygon.py +12 -12
  196. umap/tests/integration/test_websocket_sync.py +65 -3
  197. umap/tests/test_clean_tilelayer.py +83 -0
  198. umap/tests/test_datalayer.py +24 -0
  199. umap/tests/test_map_views.py +20 -0
  200. umap/tests/test_purge_purgatory.py +25 -0
  201. umap/tests/test_websocket_server.py +22 -0
  202. umap/urls.py +5 -1
  203. umap/views.py +6 -3
  204. umap/websocket_server.py +130 -27
  205. {umap_project-2.6.2.dist-info → umap_project-2.7.0.dist-info}/METADATA +18 -14
  206. {umap_project-2.6.2.dist-info → umap_project-2.7.0.dist-info}/RECORD +209 -200
  207. umap/static/umap/vendors/contextmenu/leaflet.contextmenu.min.css +0 -1
  208. umap/static/umap/vendors/contextmenu/leaflet.contextmenu.min.js +0 -7
  209. {umap_project-2.6.2.dist-info → umap_project-2.7.0.dist-info}/WHEEL +0 -0
  210. {umap_project-2.6.2.dist-info → umap_project-2.7.0.dist-info}/entry_points.txt +0 -0
  211. {umap_project-2.6.2.dist-info → umap_project-2.7.0.dist-info}/licenses/LICENSE +0 -0
@@ -92,12 +92,12 @@ export class DataLayer {
92
92
  set isDirty(status) {
93
93
  this._isDirty = status
94
94
  if (status) {
95
- this.map.addDirtyDatalayer(this)
95
+ this.map.isDirty = true
96
96
  // A layer can be made dirty by indirect action (like dragging layers)
97
97
  // we need to have it loaded before saving it.
98
98
  if (!this.isLoaded()) this.fetchData()
99
99
  } else {
100
- this.map.removeDirtyDatalayer(this)
100
+ this.map.checkDirty()
101
101
  this.isDeleted = false
102
102
  }
103
103
  }
@@ -115,6 +115,10 @@ export class DataLayer {
115
115
  return this._isDeleted
116
116
  }
117
117
 
118
+ get cssId() {
119
+ return `datalayer-${stamp(this)}`
120
+ }
121
+
118
122
  getSyncMetadata() {
119
123
  return {
120
124
  subject: 'datalayer',
@@ -235,6 +239,7 @@ export class DataLayer {
235
239
  }
236
240
 
237
241
  dataChanged() {
242
+ if (!this.hasDataLoaded()) return
238
243
  this.map.onDataLayersChanged()
239
244
  this.layer.dataChanged()
240
245
  }
@@ -242,10 +247,15 @@ export class DataLayer {
242
247
  fromGeoJSON(geojson, sync = true) {
243
248
  this.addData(geojson, sync)
244
249
  this._geojson = geojson
245
- this._dataloaded = true
250
+ this.onDataLoaded()
246
251
  this.dataChanged()
247
252
  }
248
253
 
254
+ onDataLoaded() {
255
+ this._dataloaded = true
256
+ this.renderLegend()
257
+ }
258
+
249
259
  async fromUmapGeoJSON(geojson) {
250
260
  if (geojson._storage) geojson._umap_options = geojson._storage // Retrocompat
251
261
  if (geojson._umap_options) this.setOptions(geojson._umap_options)
@@ -339,11 +349,11 @@ export class DataLayer {
339
349
  const id = stamp(this)
340
350
  if (!this.map.datalayers[id]) {
341
351
  this.map.datalayers[id] = this
342
- if (!this.map.datalayers_index.includes(this)) {
343
- this.map.datalayers_index.push(this)
344
- }
345
- this.map.onDataLayersChanged()
346
352
  }
353
+ if (!this.map.datalayers_index.includes(this)) {
354
+ this.map.datalayers_index.push(this)
355
+ }
356
+ this.map.onDataLayersChanged()
347
357
  }
348
358
 
349
359
  _dataUrl() {
@@ -384,7 +394,7 @@ export class DataLayer {
384
394
  this.indexProperties(feature)
385
395
  this.map.features_index[feature.getSlug()] = feature
386
396
  this.showFeature(feature)
387
- if (this.hasDataLoaded()) this.dataChanged()
397
+ this.dataChanged()
388
398
  }
389
399
 
390
400
  removeFeature(feature, sync) {
@@ -395,7 +405,7 @@ export class DataLayer {
395
405
  feature.disconnectFromDataLayer(this)
396
406
  this._index.splice(this._index.indexOf(id), 1)
397
407
  delete this._features[id]
398
- if (this.hasDataLoaded() && this.isVisible()) this.dataChanged()
408
+ if (this.isVisible()) this.dataChanged()
399
409
  }
400
410
 
401
411
  indexProperties(feature) {
@@ -559,7 +569,6 @@ export class DataLayer {
559
569
 
560
570
  erase() {
561
571
  this.hide()
562
- delete this.map.datalayers[stamp(this)]
563
572
  this.map.datalayers_index.splice(this.getRank(), 1)
564
573
  this.parentPane.removeChild(this.pane)
565
574
  this.map.onDataLayersChanged()
@@ -716,7 +725,7 @@ export class DataLayer {
716
725
  const remoteDataFields = [
717
726
  [
718
727
  'options.remoteData.url',
719
- { handler: 'Url', label: translate('Url'), helpEntries: 'formatURL' },
728
+ { handler: 'Url', label: translate('Url'), helpEntries: ['formatURL'] },
720
729
  ],
721
730
  [
722
731
  'options.remoteData.format',
@@ -729,7 +738,7 @@ export class DataLayer {
729
738
  {
730
739
  handler: 'Switch',
731
740
  label: translate('Dynamic'),
732
- helpEntries: 'dynamicRemoteData',
741
+ helpEntries: ['dynamicRemoteData'],
733
742
  },
734
743
  ],
735
744
  [
@@ -746,7 +755,7 @@ export class DataLayer {
746
755
  {
747
756
  handler: 'Switch',
748
757
  label: translate('Proxy request'),
749
- helpEntries: 'proxyRemoteData',
758
+ helpEntries: ['proxyRemoteData'],
750
759
  },
751
760
  ])
752
761
  remoteDataFields.push('options.remoteData.ttl')
@@ -803,13 +812,12 @@ export class DataLayer {
803
812
  this
804
813
  )
805
814
  if (this.umap_id) {
806
- const download = DomUtil.createLink(
807
- 'button umap-download',
808
- advancedButtons,
809
- translate('Download'),
810
- this._dataUrl(),
811
- '_blank'
812
- )
815
+ const filename = `${Utils.slugify(this.options.name)}.geojson`
816
+ const download = Utils.loadTemplate(`
817
+ <a class="button" href="${this._dataUrl()}" download="${filename}">
818
+ <i class="icon icon-24 icon-download"></i>${translate('Download')}
819
+ </a>`)
820
+ advancedButtons.appendChild(download)
813
821
  }
814
822
  const backButton = DomUtil.createButtonIcon(
815
823
  undefined,
@@ -874,18 +882,21 @@ export class DataLayer {
874
882
 
875
883
  async restore(version) {
876
884
  if (!this.map.editEnabled) return
877
- if (!confirm(translate('Are you sure you want to restore this version?'))) return
878
- const [geojson, response, error] = await this.map.server.get(
879
- this.getVersionUrl(version)
880
- )
881
- if (!error) {
882
- if (geojson._storage) geojson._umap_options = geojson._storage // Retrocompat.
883
- if (geojson._umap_options) this.setOptions(geojson._umap_options)
884
- this.empty()
885
- if (this.isRemoteLayer()) this.fetchRemoteData()
886
- else this.addData(geojson)
887
- this.isDirty = true
888
- }
885
+ this.map.dialog
886
+ .confirm(translate('Are you sure you want to restore this version?'))
887
+ .then(async () => {
888
+ const [geojson, response, error] = await this.map.server.get(
889
+ this.getVersionUrl(version)
890
+ )
891
+ if (!error) {
892
+ if (geojson._storage) geojson._umap_options = geojson._storage // Retrocompat.
893
+ if (geojson._umap_options) this.setOptions(geojson._umap_options)
894
+ this.empty()
895
+ if (this.isRemoteLayer()) this.fetchRemoteData()
896
+ else this.addData(geojson)
897
+ this.isDirty = true
898
+ }
899
+ })
889
900
  }
890
901
 
891
902
  featuresToGeoJSON() {
@@ -1026,7 +1037,7 @@ export class DataLayer {
1026
1037
  }
1027
1038
 
1028
1039
  async save() {
1029
- if (this.isDeleted) return this.saveDelete()
1040
+ if (this.isDeleted) return await this.saveDelete()
1030
1041
  if (!this.isLoaded()) {
1031
1042
  return
1032
1043
  }
@@ -1090,8 +1101,8 @@ export class DataLayer {
1090
1101
  if (this.umap_id) {
1091
1102
  await this.map.server.post(this.getDeleteUrl())
1092
1103
  }
1104
+ delete this.map.datalayers[stamp(this)]
1093
1105
  this.isDirty = false
1094
- this.map.continueSaving()
1095
1106
  }
1096
1107
 
1097
1108
  getMap() {
@@ -1118,10 +1129,13 @@ export class DataLayer {
1118
1129
  return 'displayName'
1119
1130
  }
1120
1131
 
1121
- renderLegend(container) {
1122
- if (this.layer.renderLegend) return this.layer.renderLegend(container)
1123
- const color = DomUtil.create('span', 'datalayer-color', container)
1124
- color.style.backgroundColor = this.getColor()
1132
+ renderLegend() {
1133
+ for (const container of document.querySelectorAll(`.${this.cssId} .datalayer-legend`)) {
1134
+ container.innerHTML = ''
1135
+ if (this.layer.renderLegend) return this.layer.renderLegend(container)
1136
+ const color = DomUtil.create('span', 'datalayer-color', container)
1137
+ color.style.backgroundColor = this.getColor()
1138
+ }
1125
1139
  }
1126
1140
 
1127
1141
  renderToolbox(container) {
@@ -1160,8 +1174,11 @@ export class DataLayer {
1160
1174
  'click',
1161
1175
  function () {
1162
1176
  if (!this.isVisible()) return
1163
- if (!confirm(translate('Are you sure you want to delete this layer?'))) return
1164
- this._delete()
1177
+ this.map.dialog
1178
+ .confirm(translate('Are you sure you want to delete this layer?'))
1179
+ .then(() => {
1180
+ this._delete()
1181
+ })
1165
1182
  },
1166
1183
  this
1167
1184
  )
@@ -1,4 +1,4 @@
1
- /* Uses globals for: csv2geojson, osmtogeojson, GeoRSSToGeoJSON (not available as ESM) */
1
+ /* Uses globals for: csv2geojson, osmtogeojson (not available as ESM) */
2
2
  import { translate } from './i18n.js'
3
3
 
4
4
  export const EXPORT_FORMATS = {
@@ -115,7 +115,8 @@ export class Formatter {
115
115
  }
116
116
 
117
117
  async fromGeoRSS(str) {
118
- return GeoRSSToGeoJSON(this.toDom(str))
118
+ const GeoRSSToGeoJSON = await import('../../vendors/georsstogeojson/GeoRSSToGeoJSON.js')
119
+ return GeoRSSToGeoJSON.parse(this.toDom(str))
119
120
  }
120
121
 
121
122
  toDom(x) {
@@ -9,6 +9,7 @@ import {
9
9
  } from './autocomplete.js'
10
10
  import Browser from './browser.js'
11
11
  import Caption from './caption.js'
12
+ import ContextMenu from './ui/contextmenu.js'
12
13
  import Facets from './facets.js'
13
14
  import { Formatter } from './formatter.js'
14
15
  import Help from './help.js'
@@ -43,6 +44,7 @@ window.U = {
43
44
  AutocompleteDatalist,
44
45
  Browser,
45
46
  Caption,
47
+ ContextMenu,
46
48
  DataLayer,
47
49
  DataLayerPermissions,
48
50
  Dialog,
@@ -69,6 +69,9 @@ export default class Importer {
69
69
  case 'communesfr':
70
70
  import('./importers/communesfr.js').then(register)
71
71
  break
72
+ case 'cadastrefr':
73
+ import('./importers/cadastrefr.js').then(register)
74
+ break
72
75
  case 'overpass':
73
76
  import('./importers/overpass.js').then(register)
74
77
  break
@@ -0,0 +1,62 @@
1
+ import { DomUtil } from '../../../vendors/leaflet/leaflet-src.esm.js'
2
+ import { BaseAjax, SingleMixin } from '../autocomplete.js'
3
+ import * as Util from '../utils.js'
4
+ import { AutocompleteCommunes } from './communesfr.js'
5
+
6
+ const TEMPLATE = `
7
+ <h3>Cadastre</h3>
8
+ <p>Importer les données cadastrales d’une commune française.</p>
9
+ <select name="theme">
10
+ <option value="batiments">Bâtiments</option>
11
+ <option value="communes">Communes</option>
12
+ <option value="feuilles">Feuilles</option>
13
+ <option value="lieux_dits">Lieux dits</option>
14
+ <option value="parcelles" selected>Parcelles</option>
15
+ <option value="prefixes_sections">Préfixes sections</option>
16
+ <option value="sections">Sections</option>
17
+ <option value="subdivisions_fiscales">Subdivisions fiscales</option>
18
+ </select>
19
+ <label id="boundary">
20
+ </label>
21
+ `
22
+
23
+ export class Importer {
24
+ constructor(map, options) {
25
+ this.name = options.name || 'Cadastre'
26
+ this.id = 'cadastrefr'
27
+ }
28
+
29
+ async open(importer) {
30
+ let boundary = null
31
+ let boundaryName = null
32
+ const container = DomUtil.create('div')
33
+ container.innerHTML = TEMPLATE
34
+ const select = container.querySelector('select')
35
+ const options = {
36
+ placeholder: 'Nom ou code INSEE…',
37
+ url: 'https://geo.api.gouv.fr/communes?nom={q}&limit=5',
38
+ on_select: (choice) => {
39
+ boundary = choice.item.value
40
+ boundaryName = choice.item.label
41
+ },
42
+ }
43
+ this.autocomplete = new AutocompleteCommunes(container, options)
44
+
45
+ const confirm = (form) => {
46
+ if (!boundary || !form.theme) {
47
+ Alert.error(translate('Please choose a theme and a boundary first.'))
48
+ return
49
+ }
50
+ importer.url = `https://cadastre.data.gouv.fr/bundler/cadastre-etalab/communes/${boundary}/geojson/${form.theme}`
51
+ importer.format = 'geojson'
52
+ importer.layerName = `${boundaryName} — ${select.options[select.selectedIndex].textContent}`
53
+ }
54
+
55
+ importer.dialog
56
+ .open({
57
+ template: container,
58
+ className: `${this.id} importer dark`,
59
+ })
60
+ .then(confirm)
61
+ }
62
+ }
@@ -1,13 +1,25 @@
1
1
  import { DomUtil } from '../../../vendors/leaflet/leaflet-src.esm.js'
2
2
  import { BaseAjax, SingleMixin } from '../autocomplete.js'
3
+ import * as Util from '../utils.js'
3
4
 
4
- class Autocomplete extends SingleMixin(BaseAjax) {
5
+ export class AutocompleteCommunes extends SingleMixin(BaseAjax) {
5
6
  createResult(item) {
6
7
  return super.createResult({
7
8
  value: item.code,
8
9
  label: `${item.nom} (${item.code})`,
9
10
  })
10
11
  }
12
+
13
+ buildUrl(value) {
14
+ let url = this.url
15
+ let options = { q: encodeURIComponent(value) }
16
+ const re = /^(0[1-9]|[1-9][ABab\d])\d{3}$/gm
17
+ if (re.test(value)) {
18
+ url = "https://geo.api.gouv.fr/communes?code={code}&limit=5"
19
+ options = { code: encodeURIComponent(value) }
20
+ }
21
+ return Util.template(url, options)
22
+ }
11
23
  }
12
24
 
13
25
  export class Importer {
@@ -25,7 +37,7 @@ export class Importer {
25
37
  textContent: "Importer les contours d'une commune française.",
26
38
  })
27
39
  const options = {
28
- placeholder: 'Commune…',
40
+ placeholder: 'Nom ou code INSEE…',
29
41
  url: 'https://geo.api.gouv.fr/communes?nom={q}&limit=5',
30
42
  on_select: (choice) => {
31
43
  importer.url = `https://geo.api.gouv.fr/communes?code=${choice.item.value}&format=geojson&geometry=contour`
@@ -34,7 +46,7 @@ export class Importer {
34
46
  importer.dialog.close()
35
47
  },
36
48
  }
37
- this.autocomplete = new Autocomplete(container, options)
49
+ this.autocomplete = new AutocompleteCommunes(container, options)
38
50
 
39
51
  importer.dialog.open({
40
52
  template: container,
@@ -42,101 +42,129 @@ export class MapPermissions {
42
42
  return !this.map.options.permissions.owner
43
43
  }
44
44
 
45
- getMap() {
46
- return this.map
47
- }
48
-
49
- edit() {
50
- if (this.map.options.editMode !== 'advanced') return
51
- if (!this.map.options.umap_id) {
52
- return Alert.info(translate('Please save the map first'))
53
- }
54
- const container = DomUtil.create('div', 'permissions-panel')
45
+ _editAnonymous(container) {
55
46
  const fields = []
56
- DomUtil.createTitle(container, translate('Update permissions'), 'icon-key')
57
- if (this.isAnonymousMap()) {
47
+ if (this.isOwner()) {
48
+ fields.push([
49
+ 'options.edit_status',
50
+ {
51
+ handler: 'IntSelect',
52
+ label: translate('Who can edit'),
53
+ selectOptions: this.map.options.edit_statuses,
54
+ },
55
+ ])
56
+ const builder = new U.FormBuilder(this, fields)
57
+ const form = builder.build()
58
+ container.appendChild(form)
59
+
58
60
  if (this.options.anonymous_edit_url) {
59
- const helpText = `${translate('Secret edit link:')}<br>${
61
+ DomUtil.createCopiableInput(
62
+ container,
63
+ translate('Secret edit link:'),
60
64
  this.options.anonymous_edit_url
61
- }`
62
- DomUtil.element({
63
- tagName: 'p',
64
- className: 'help-text',
65
- innerHTML: helpText,
66
- parent: container,
67
- })
68
- fields.push([
69
- 'options.edit_status',
70
- {
71
- handler: 'IntSelect',
72
- label: translate('Who can edit'),
73
- selectOptions: this.map.options.edit_statuses,
74
- helpText: helpText,
75
- },
76
- ])
65
+ )
77
66
  }
78
- } else {
79
- if (this.isOwner()) {
80
- fields.push([
81
- 'options.edit_status',
82
- {
83
- handler: 'IntSelect',
84
- label: translate('Who can edit'),
85
- selectOptions: this.map.options.edit_statuses,
86
- },
87
- ])
88
- fields.push([
89
- 'options.share_status',
67
+
68
+ if (this.map.options.user?.id) {
69
+ // We have a user, and this user has come through here, so they can edit the map, so let's allow to own the map.
70
+ // Note: real check is made on the back office anyway.
71
+ const advancedActions = DomUtil.createFieldset(
72
+ container,
73
+ translate('Advanced actions')
74
+ )
75
+ const advancedButtons = DomUtil.create('div', 'button-bar', advancedActions)
76
+ DomUtil.createButton(
77
+ 'button',
78
+ advancedButtons,
79
+ translate('Attach the map to my account'),
80
+ this.attach,
81
+ this
82
+ )
83
+ }
84
+ }
85
+ }
86
+
87
+ _editWithOwner(container) {
88
+ const topFields = []
89
+ const collaboratorsFields = []
90
+ const fieldset = Utils.loadTemplate(
91
+ `<fieldset class="separator"><legend>${translate('Map')}</legend></fieldset>`
92
+ )
93
+ container.appendChild(fieldset)
94
+ if (this.isOwner()) {
95
+ topFields.push([
96
+ 'options.edit_status',
97
+ {
98
+ handler: 'IntSelect',
99
+ label: translate('Who can edit'),
100
+ selectOptions: this.map.options.edit_statuses,
101
+ },
102
+ ])
103
+ topFields.push([
104
+ 'options.share_status',
105
+ {
106
+ handler: 'IntSelect',
107
+ label: translate('Who can view'),
108
+ selectOptions: this.map.options.share_statuses,
109
+ },
110
+ ])
111
+ collaboratorsFields.push([
112
+ 'options.owner',
113
+ { handler: 'ManageOwner', label: translate("Map's owner") },
114
+ ])
115
+ if (this.map.options.user?.teams?.length) {
116
+ collaboratorsFields.push([
117
+ 'options.team',
90
118
  {
91
- handler: 'IntSelect',
92
- label: translate('Who can view'),
93
- selectOptions: this.map.options.share_statuses,
119
+ handler: 'ManageTeam',
120
+ label: translate('Attach map to a team'),
121
+ teams: this.map.options.user.teams,
94
122
  },
95
123
  ])
96
- fields.push([
97
- 'options.owner',
98
- { handler: 'ManageOwner', label: translate("Map's owner") },
99
- ])
100
- if (this.map.options.user?.teams?.length) {
101
- fields.push([
102
- 'options.team',
103
- {
104
- handler: 'ManageTeam',
105
- label: translate('Attach map to a team'),
106
- teams: this.map.options.user.teams,
107
- },
108
- ])
109
- }
110
124
  }
111
- fields.push([
112
- 'options.editors',
113
- { handler: 'ManageEditors', label: translate("Map's editors") },
114
- ])
115
125
  }
126
+ collaboratorsFields.push([
127
+ 'options.editors',
128
+ { handler: 'ManageEditors', label: translate("Map's editors") },
129
+ ])
116
130
 
117
- const builder = new U.FormBuilder(this, fields)
131
+ const builder = new U.FormBuilder(this, topFields)
118
132
  const form = builder.build()
119
133
  container.appendChild(form)
120
- if (this.isAnonymousMap() && this.map.options.user) {
121
- // We have a user, and this user has come through here, so they can edit the map, so let's allow to own the map.
122
- // Note: real check is made on the back office anyway.
123
- const advancedActions = DomUtil.createFieldset(
124
- container,
125
- translate('Advanced actions')
134
+ if (collaboratorsFields.length) {
135
+ const fieldset = Utils.loadTemplate(
136
+ `<fieldset class="separator"><legend>${translate('Manage collaborators')}</legend></fieldset>`
126
137
  )
127
- const advancedButtons = DomUtil.create('div', 'button-bar', advancedActions)
128
- DomUtil.createButton(
129
- 'button',
130
- advancedButtons,
131
- translate('Attach the map to my account'),
132
- this.attach,
133
- this
138
+ container.appendChild(fieldset)
139
+ const builder = new U.FormBuilder(this, collaboratorsFields)
140
+ const form = builder.build()
141
+ container.appendChild(form)
142
+ }
143
+ }
144
+
145
+ _editDatalayers(container) {
146
+ if (this.map.hasLayers()) {
147
+ const fieldset = Utils.loadTemplate(
148
+ `<fieldset class="separator"><legend>${translate('Datalayers')}</legend></fieldset>`
134
149
  )
150
+ container.appendChild(fieldset)
151
+ this.map.eachDataLayer((datalayer) => {
152
+ datalayer.permissions.edit(fieldset)
153
+ })
135
154
  }
136
- DomUtil.add('h4', '', container, translate('Datalayers'))
137
- this.map.eachDataLayer((datalayer) => {
138
- datalayer.permissions.edit(container)
139
- })
155
+ }
156
+
157
+ edit() {
158
+ if (this.map.options.editMode !== 'advanced') return
159
+ if (!this.map.options.umap_id) {
160
+ Alert.info(translate('Please save the map first'))
161
+ return
162
+ }
163
+ const container = DomUtil.create('div', 'permissions-panel')
164
+ DomUtil.createTitle(container, translate('Update permissions'), 'icon-key')
165
+ if (this.isAnonymousMap()) this._editAnonymous(container)
166
+ else this._editWithOwner(container)
167
+ this._editDatalayers(container)
140
168
  this.map.editPanel.open({ content: container, className: 'dark' })
141
169
  }
142
170
 
@@ -150,15 +178,16 @@ export class MapPermissions {
150
178
  }
151
179
 
152
180
  async save() {
153
- if (!this.isDirty) return this.map.continueSaving()
181
+ if (!this.isDirty) return
154
182
  const formData = new FormData()
155
183
  if (!this.isAnonymousMap() && this.options.editors) {
156
184
  const editors = this.options.editors.map((u) => u.id)
157
185
  for (let i = 0; i < this.options.editors.length; i++)
158
186
  formData.append('editors', this.options.editors[i].id)
159
187
  }
160
- if (this.isOwner() || this.isAnonymousMap())
188
+ if (this.isOwner() || this.isAnonymousMap()) {
161
189
  formData.append('edit_status', this.options.edit_status)
190
+ }
162
191
  if (this.isOwner()) {
163
192
  formData.append('owner', this.options.owner?.id)
164
193
  formData.append('team', this.options.team?.id || '')
@@ -172,7 +201,6 @@ export class MapPermissions {
172
201
  if (!error) {
173
202
  this.commit()
174
203
  this.isDirty = false
175
- this.map.continueSaving()
176
204
  this.map.fire('postsync')
177
205
  }
178
206
  }
@@ -197,9 +225,11 @@ export class MapPermissions {
197
225
  }
198
226
 
199
227
  getShareStatusDisplay() {
200
- return Object.fromEntries(this.map.options.share_statuses)[
201
- this.options.share_status
202
- ]
228
+ if (this.map.options.share_statuses) {
229
+ return Object.fromEntries(this.map.options.share_statuses)[
230
+ this.options.share_status
231
+ ]
232
+ }
203
233
  }
204
234
  }
205
235
 
@@ -225,7 +255,7 @@ export class DataLayerPermissions {
225
255
  return this._isDirty
226
256
  }
227
257
 
228
- getMap() {
258
+ get map() {
229
259
  return this.datalayer.map
230
260
  }
231
261
 
@@ -238,7 +268,7 @@ export class DataLayerPermissions {
238
268
  label: translate('Who can edit "{layer}"', {
239
269
  layer: this.datalayer.getName(),
240
270
  }),
241
- selectOptions: this.datalayer.map.options.datalayer_edit_statuses,
271
+ selectOptions: this.map.options.datalayer_edit_statuses,
242
272
  },
243
273
  ],
244
274
  ]
@@ -250,16 +280,17 @@ export class DataLayerPermissions {
250
280
  }
251
281
 
252
282
  getUrl() {
253
- return Utils.template(this.datalayer.map.options.urls.datalayer_permissions, {
254
- map_id: this.datalayer.map.options.umap_id,
283
+ return this.map.urls.get('datalayer_permissions', {
284
+ map_id: this.map.options.umap_id,
255
285
  pk: this.datalayer.umap_id,
256
286
  })
257
287
  }
288
+
258
289
  async save() {
259
- if (!this.isDirty) return this.datalayer.map.continueSaving()
290
+ if (!this.isDirty) return
260
291
  const formData = new FormData()
261
292
  formData.append('edit_status', this.options.edit_status)
262
- const [data, response, error] = await this.datalayer.map.server.post(
293
+ const [data, response, error] = await this.map.server.post(
263
294
  this.getUrl(),
264
295
  {},
265
296
  formData
@@ -267,7 +298,6 @@ export class DataLayerPermissions {
267
298
  if (!error) {
268
299
  this.commit()
269
300
  this.isDirty = false
270
- this.datalayer.map.continueSaving()
271
301
  }
272
302
  }
273
303
 
@@ -3,6 +3,7 @@ import { translate } from '../../i18n.js'
3
3
  import { LayerMixin } from './base.js'
4
4
  import * as Utils from '../../utils.js'
5
5
  import { CircleMarker } from '../ui.js'
6
+ import colorbrewer from '../../../../vendors/colorbrewer/colorbrewer.js'
6
7
 
7
8
  // Layer where each feature color is relative to the others,
8
9
  // so we need all features before behing able to set one
@@ -74,6 +75,7 @@ const ClassifiedMixin = {
74
75
  },
75
76
 
76
77
  renderLegend: function (container) {
78
+ if (!this.datalayer.hasDataLoaded()) return
77
79
  const parent = DomUtil.create('ul', '', container)
78
80
  const items = this.getLegendItems()
79
81
  for (const [color, label] of items) {