umap-project 2.8.2__py3-none-any.whl → 2.9.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 (260) hide show
  1. umap/__init__.py +1 -1
  2. umap/admin.py +15 -2
  3. umap/asgi.py +12 -7
  4. umap/context_processors.py +1 -0
  5. umap/locale/br/LC_MESSAGES/django.mo +0 -0
  6. umap/locale/br/LC_MESSAGES/django.po +111 -67
  7. umap/locale/cs_CZ/LC_MESSAGES/django.mo +0 -0
  8. umap/locale/cs_CZ/LC_MESSAGES/django.po +110 -66
  9. umap/locale/el/LC_MESSAGES/django.mo +0 -0
  10. umap/locale/el/LC_MESSAGES/django.po +129 -85
  11. umap/locale/en/LC_MESSAGES/django.po +103 -60
  12. umap/locale/es/LC_MESSAGES/django.mo +0 -0
  13. umap/locale/es/LC_MESSAGES/django.po +114 -69
  14. umap/locale/fr/LC_MESSAGES/django.mo +0 -0
  15. umap/locale/fr/LC_MESSAGES/django.po +105 -61
  16. umap/locale/gl/LC_MESSAGES/django.mo +0 -0
  17. umap/locale/gl/LC_MESSAGES/django.po +216 -171
  18. umap/locale/it/LC_MESSAGES/django.mo +0 -0
  19. umap/locale/it/LC_MESSAGES/django.po +142 -98
  20. umap/locale/nl/LC_MESSAGES/django.mo +0 -0
  21. umap/locale/nl/LC_MESSAGES/django.po +196 -151
  22. umap/locale/pt/LC_MESSAGES/django.mo +0 -0
  23. umap/locale/pt/LC_MESSAGES/django.po +115 -71
  24. umap/locale/zh_TW/LC_MESSAGES/django.mo +0 -0
  25. umap/locale/zh_TW/LC_MESSAGES/django.po +109 -65
  26. umap/management/commands/empty_trash.py +12 -1
  27. umap/migrations/0026_datalayer_modified_at_datalayer_share_status.py +26 -0
  28. umap/models.py +43 -13
  29. umap/settings/base.py +5 -2
  30. umap/static/umap/base.css +5 -2
  31. umap/static/umap/content.css +2 -22
  32. umap/static/umap/css/bar.css +39 -10
  33. umap/static/umap/css/contextmenu.css +14 -2
  34. umap/static/umap/css/form.css +33 -39
  35. umap/static/umap/css/icon.css +47 -5
  36. umap/static/umap/css/panel.css +20 -2
  37. umap/static/umap/css/popup.css +0 -1
  38. umap/static/umap/css/tooltip.css +33 -31
  39. umap/static/umap/img/16-white.svg +5 -3
  40. umap/static/umap/img/16.svg +1 -1
  41. umap/static/umap/img/24-white.svg +17 -16
  42. umap/static/umap/img/24.svg +29 -18
  43. umap/static/umap/img/providers/bitbucket.png +0 -0
  44. umap/static/umap/img/providers/github.png +0 -0
  45. umap/static/umap/img/providers/keycloak.png +0 -0
  46. umap/static/umap/img/providers/openstreetmap-oauth2.png +0 -0
  47. umap/static/umap/img/providers/twitter-oauth2.png +0 -0
  48. umap/static/umap/img/source/16-white.svg +6 -4
  49. umap/static/umap/img/source/16.svg +1 -1
  50. umap/static/umap/img/source/24-white.svg +20 -18
  51. umap/static/umap/img/source/24.svg +30 -19
  52. umap/static/umap/js/components/alerts/alert.js +4 -1
  53. umap/static/umap/js/modules/browser.js +8 -8
  54. umap/static/umap/js/modules/caption.js +30 -7
  55. umap/static/umap/js/modules/data/features.js +101 -56
  56. umap/static/umap/js/modules/data/layer.js +108 -83
  57. umap/static/umap/js/modules/form/builder.js +242 -0
  58. umap/static/umap/js/modules/form/fields.js +1346 -0
  59. umap/static/umap/js/modules/formatter.js +9 -8
  60. umap/static/umap/js/modules/help.js +20 -24
  61. umap/static/umap/js/modules/importer.js +6 -3
  62. umap/static/umap/js/modules/permissions.js +11 -6
  63. umap/static/umap/js/modules/rendering/icon.js +5 -1
  64. umap/static/umap/js/modules/rendering/layers/classified.js +12 -8
  65. umap/static/umap/js/modules/rendering/layers/cluster.js +11 -1
  66. umap/static/umap/js/modules/rendering/map.js +1 -23
  67. umap/static/umap/js/modules/rendering/ui.js +20 -38
  68. umap/static/umap/js/modules/rules.js +3 -2
  69. umap/static/umap/js/modules/saving.js +5 -0
  70. umap/static/umap/js/modules/schema.js +8 -6
  71. umap/static/umap/js/modules/share.js +3 -3
  72. umap/static/umap/js/modules/sync/engine.js +56 -26
  73. umap/static/umap/js/modules/sync/updaters.js +15 -6
  74. umap/static/umap/js/modules/sync/websocket.js +50 -37
  75. umap/static/umap/js/modules/tableeditor.js +3 -2
  76. umap/static/umap/js/modules/ui/bar.js +101 -9
  77. umap/static/umap/js/modules/ui/base.js +7 -24
  78. umap/static/umap/js/modules/ui/contextmenu.js +9 -2
  79. umap/static/umap/js/modules/ui/panel.js +5 -1
  80. umap/static/umap/js/modules/ui/tooltip.js +19 -11
  81. umap/static/umap/js/modules/umap.js +121 -68
  82. umap/static/umap/js/modules/utils.js +196 -12
  83. umap/static/umap/js/umap.controls.js +11 -353
  84. umap/static/umap/locale/am_ET.js +17 -5
  85. umap/static/umap/locale/am_ET.json +17 -5
  86. umap/static/umap/locale/ar.js +17 -5
  87. umap/static/umap/locale/ar.json +17 -5
  88. umap/static/umap/locale/ast.js +17 -5
  89. umap/static/umap/locale/ast.json +17 -5
  90. umap/static/umap/locale/bg.js +17 -5
  91. umap/static/umap/locale/bg.json +17 -5
  92. umap/static/umap/locale/br.js +33 -20
  93. umap/static/umap/locale/br.json +33 -20
  94. umap/static/umap/locale/ca.js +17 -5
  95. umap/static/umap/locale/ca.json +17 -5
  96. umap/static/umap/locale/cs_CZ.js +15 -5
  97. umap/static/umap/locale/cs_CZ.json +15 -5
  98. umap/static/umap/locale/da.js +17 -5
  99. umap/static/umap/locale/da.json +17 -5
  100. umap/static/umap/locale/de.js +17 -5
  101. umap/static/umap/locale/de.json +17 -5
  102. umap/static/umap/locale/el.js +63 -51
  103. umap/static/umap/locale/el.json +63 -51
  104. umap/static/umap/locale/en.js +15 -5
  105. umap/static/umap/locale/en.json +15 -5
  106. umap/static/umap/locale/en_US.json +17 -5
  107. umap/static/umap/locale/es.js +25 -13
  108. umap/static/umap/locale/es.json +25 -13
  109. umap/static/umap/locale/et.js +17 -5
  110. umap/static/umap/locale/et.json +17 -5
  111. umap/static/umap/locale/eu.js +17 -5
  112. umap/static/umap/locale/eu.json +17 -5
  113. umap/static/umap/locale/fa_IR.js +17 -5
  114. umap/static/umap/locale/fa_IR.json +17 -5
  115. umap/static/umap/locale/fi.js +17 -5
  116. umap/static/umap/locale/fi.json +17 -5
  117. umap/static/umap/locale/fr.js +16 -6
  118. umap/static/umap/locale/fr.json +16 -6
  119. umap/static/umap/locale/gl.js +357 -345
  120. umap/static/umap/locale/gl.json +357 -345
  121. umap/static/umap/locale/he.js +17 -5
  122. umap/static/umap/locale/he.json +17 -5
  123. umap/static/umap/locale/hr.js +17 -5
  124. umap/static/umap/locale/hr.json +17 -5
  125. umap/static/umap/locale/hu.js +14 -5
  126. umap/static/umap/locale/hu.json +14 -5
  127. umap/static/umap/locale/id.js +17 -5
  128. umap/static/umap/locale/id.json +17 -5
  129. umap/static/umap/locale/is.js +17 -5
  130. umap/static/umap/locale/is.json +17 -5
  131. umap/static/umap/locale/it.js +125 -113
  132. umap/static/umap/locale/it.json +125 -113
  133. umap/static/umap/locale/ja.js +17 -5
  134. umap/static/umap/locale/ja.json +17 -5
  135. umap/static/umap/locale/ko.js +17 -5
  136. umap/static/umap/locale/ko.json +17 -5
  137. umap/static/umap/locale/lt.js +17 -5
  138. umap/static/umap/locale/lt.json +17 -5
  139. umap/static/umap/locale/ms.js +17 -5
  140. umap/static/umap/locale/ms.json +17 -5
  141. umap/static/umap/locale/nl.js +132 -119
  142. umap/static/umap/locale/nl.json +132 -119
  143. umap/static/umap/locale/no.js +17 -5
  144. umap/static/umap/locale/no.json +17 -5
  145. umap/static/umap/locale/pl.js +17 -5
  146. umap/static/umap/locale/pl.json +17 -5
  147. umap/static/umap/locale/pl_PL.json +17 -5
  148. umap/static/umap/locale/pt.js +38 -25
  149. umap/static/umap/locale/pt.json +38 -25
  150. umap/static/umap/locale/pt_BR.js +17 -5
  151. umap/static/umap/locale/pt_BR.json +17 -5
  152. umap/static/umap/locale/pt_PT.js +17 -5
  153. umap/static/umap/locale/pt_PT.json +17 -5
  154. umap/static/umap/locale/ro.js +17 -5
  155. umap/static/umap/locale/ro.json +17 -5
  156. umap/static/umap/locale/ru.js +17 -5
  157. umap/static/umap/locale/ru.json +17 -5
  158. umap/static/umap/locale/sk_SK.js +17 -5
  159. umap/static/umap/locale/sk_SK.json +17 -5
  160. umap/static/umap/locale/sl.js +17 -5
  161. umap/static/umap/locale/sl.json +17 -5
  162. umap/static/umap/locale/sr.js +17 -5
  163. umap/static/umap/locale/sr.json +17 -5
  164. umap/static/umap/locale/sv.js +17 -5
  165. umap/static/umap/locale/sv.json +17 -5
  166. umap/static/umap/locale/th_TH.js +17 -5
  167. umap/static/umap/locale/th_TH.json +17 -5
  168. umap/static/umap/locale/tr.js +17 -5
  169. umap/static/umap/locale/tr.json +17 -5
  170. umap/static/umap/locale/uk_UA.js +17 -5
  171. umap/static/umap/locale/uk_UA.json +17 -5
  172. umap/static/umap/locale/vi.js +17 -5
  173. umap/static/umap/locale/vi.json +17 -5
  174. umap/static/umap/locale/vi_VN.json +17 -5
  175. umap/static/umap/locale/zh.js +17 -5
  176. umap/static/umap/locale/zh.json +17 -5
  177. umap/static/umap/locale/zh_CN.json +17 -5
  178. umap/static/umap/locale/zh_TW.Big5.json +17 -5
  179. umap/static/umap/locale/zh_TW.js +15 -5
  180. umap/static/umap/locale/zh_TW.json +15 -5
  181. umap/static/umap/map.css +29 -76
  182. umap/static/umap/nav.css +6 -3
  183. umap/static/umap/unittests/utils.js +14 -0
  184. umap/static/umap/vars.css +3 -0
  185. umap/static/umap/vendors/dompurify/purify.es.js +138 -354
  186. umap/static/umap/vendors/dompurify/purify.es.mjs.map +1 -1
  187. umap/static/umap/vendors/editable/Leaflet.Editable.js +1 -0
  188. umap/sync/__init__.py +0 -0
  189. umap/sync/app.py +187 -0
  190. umap/sync/payloads.py +56 -0
  191. umap/templates/auth/user_detail.html +4 -0
  192. umap/templates/auth/user_form.html +9 -6
  193. umap/templates/auth/user_stars.html +4 -0
  194. umap/templates/base.html +1 -1
  195. umap/templates/registration/login.html +2 -5
  196. umap/templates/umap/about.html +5 -0
  197. umap/templates/umap/about_summary.html +2 -2
  198. umap/templates/umap/components/provider.html +8 -0
  199. umap/templates/umap/content_footer.html +1 -1
  200. umap/templates/umap/css.html +0 -2
  201. umap/templates/umap/js.html +0 -4
  202. umap/templates/umap/map_detail.html +1 -1
  203. umap/templates/umap/password_change.html +4 -0
  204. umap/templates/umap/password_change_done.html +4 -0
  205. umap/templates/umap/search.html +4 -0
  206. umap/templates/umap/search_bar.html +1 -0
  207. umap/templates/umap/team_confirm_delete.html +4 -0
  208. umap/templates/umap/team_detail.html +4 -0
  209. umap/templates/umap/team_form.html +4 -0
  210. umap/templates/umap/user_dashboard.html +1 -1
  211. umap/templates/umap/user_teams.html +4 -0
  212. umap/tests/base.py +3 -1
  213. umap/tests/integration/conftest.py +16 -23
  214. umap/tests/integration/test_anonymous_owned_map.py +2 -2
  215. umap/tests/integration/test_basics.py +4 -7
  216. umap/tests/integration/test_caption.py +1 -0
  217. umap/tests/integration/test_categorized_layer.py +4 -8
  218. umap/tests/integration/test_choropleth.py +1 -1
  219. umap/tests/integration/test_conditional_rules.py +3 -3
  220. umap/tests/integration/test_draw_polygon.py +14 -22
  221. umap/tests/integration/test_draw_polyline.py +6 -14
  222. umap/tests/integration/test_edit_datalayer.py +11 -11
  223. umap/tests/integration/test_edit_map.py +30 -4
  224. umap/tests/integration/test_edit_marker.py +5 -5
  225. umap/tests/integration/test_edit_polygon.py +6 -6
  226. umap/tests/integration/test_features_id_generation.py +2 -6
  227. umap/tests/integration/test_import.py +115 -29
  228. umap/tests/integration/test_optimistic_merge.py +1 -0
  229. umap/tests/integration/test_owned_map.py +1 -1
  230. umap/tests/integration/test_picto.py +8 -8
  231. umap/tests/integration/test_save.py +3 -2
  232. umap/tests/integration/test_star.py +13 -9
  233. umap/tests/integration/test_tableeditor.py +8 -7
  234. umap/tests/integration/test_view_marker.py +10 -0
  235. umap/tests/integration/test_websocket_sync.py +239 -64
  236. umap/tests/settings.py +2 -0
  237. umap/tests/test_datalayer.py +2 -3
  238. umap/tests/test_datalayer_views.py +20 -1
  239. umap/tests/test_empty_trash.py +10 -3
  240. umap/tests/test_map_views.py +11 -0
  241. umap/utils.py +27 -11
  242. umap/views.py +37 -6
  243. {umap_project-2.8.2.dist-info → umap_project-2.9.0.dist-info}/METADATA +22 -22
  244. {umap_project-2.8.2.dist-info → umap_project-2.9.0.dist-info}/RECORD +247 -248
  245. {umap_project-2.8.2.dist-info → umap_project-2.9.0.dist-info}/WHEEL +1 -1
  246. umap/management/commands/run_websocket_server.py +0 -23
  247. umap/settings/local_s3.py +0 -45
  248. umap/static/umap/bitbucket.png +0 -0
  249. umap/static/umap/github.png +0 -0
  250. umap/static/umap/js/umap.forms.js +0 -1242
  251. umap/static/umap/keycloak.png +0 -0
  252. umap/static/umap/openstreetmap.png +0 -0
  253. umap/static/umap/twitter.png +0 -0
  254. umap/static/umap/vendors/formbuilder/Leaflet.FormBuilder.js +0 -468
  255. umap/static/umap/vendors/toolbar/leaflet.toolbar.css +0 -1
  256. umap/static/umap/vendors/toolbar/leaflet.toolbar.js +0 -1
  257. umap/tests/test_websocket_server.py +0 -22
  258. umap/websocket_server.py +0 -202
  259. {umap_project-2.8.2.dist-info → umap_project-2.9.0.dist-info}/entry_points.txt +0 -0
  260. {umap_project-2.8.2.dist-info → umap_project-2.9.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,5 +1,3 @@
1
- // Uses U.FormBuilder not available as ESM
2
-
3
1
  // FIXME: this module should not depend on Leaflet
4
2
  import {
5
3
  DomUtil,
@@ -22,6 +20,7 @@ import { Point, LineString, Polygon } from './features.js'
22
20
  import TableEditor from '../tableeditor.js'
23
21
  import { ServerStored } from '../saving.js'
24
22
  import * as Schema from '../schema.js'
23
+ import { MutatingForm } from '../form/builder.js'
25
24
 
26
25
  export const LAYER_TYPES = [
27
26
  DefaultLayer,
@@ -46,8 +45,6 @@ export class DataLayer extends ServerStored {
46
45
  this._features = {}
47
46
  this._geojson = null
48
47
  this._propertiesIndex = []
49
- this._loaded = false // Are layer metadata loaded
50
- this._dataloaded = false // Are layer data loaded
51
48
 
52
49
  this._leafletMap = leafletMap
53
50
  this.parentPane = this._leafletMap.getPane('overlayPane')
@@ -86,6 +83,7 @@ export class DataLayer extends ServerStored {
86
83
  this.connectToMap()
87
84
  this.permissions = new DataLayerPermissions(this._umap, this)
88
85
 
86
+ this._needsFetch = this.createdOnServer
89
87
  if (!this.createdOnServer) {
90
88
  if (this.showAtLoad()) this.show()
91
89
  }
@@ -244,21 +242,22 @@ export class DataLayer extends ServerStored {
244
242
  }
245
243
 
246
244
  dataChanged() {
247
- if (!this.hasDataLoaded()) return
245
+ if (!this.isLoaded()) return
248
246
  this._umap.onDataLayersChanged()
249
247
  this.layer.dataChanged()
250
248
  }
251
249
 
252
250
  fromGeoJSON(geojson, sync = true) {
251
+ if (!geojson) return []
253
252
  const features = this.addData(geojson, sync)
254
253
  this._geojson = geojson
254
+ this._needsFetch = false
255
255
  this.onDataLoaded()
256
256
  this.dataChanged()
257
257
  return features
258
258
  }
259
259
 
260
260
  onDataLoaded() {
261
- this._dataloaded = true
262
261
  this.renderLegend()
263
262
  }
264
263
 
@@ -268,7 +267,6 @@ export class DataLayer extends ServerStored {
268
267
  if (geojson._umap_options) this.setOptions(geojson._umap_options)
269
268
  if (this.isRemoteLayer()) await this.fetchRemoteData()
270
269
  else this.fromGeoJSON(geojson, false)
271
- this._loaded = true
272
270
  }
273
271
 
274
272
  clear() {
@@ -283,7 +281,9 @@ export class DataLayer extends ServerStored {
283
281
  }
284
282
 
285
283
  backupData() {
286
- this._geojson_bk = Utils.CopyJSON(this._geojson)
284
+ if (this._geojson) {
285
+ this._geojson_bk = Utils.CopyJSON(this._geojson)
286
+ }
287
287
  }
288
288
 
289
289
  reindex() {
@@ -303,29 +303,47 @@ export class DataLayer extends ServerStored {
303
303
  return this.isRemoteLayer() && Boolean(this.options.remoteData?.dynamic)
304
304
  }
305
305
 
306
+ async getUrl(url, initialUrl) {
307
+ const response = await this._umap.request.get(url)
308
+ return new Promise((resolve) => {
309
+ if (response?.ok) return resolve(response.text())
310
+ Alert.error(
311
+ translate('Cannot load remote data for layer "{layer}" with url "{url}"', {
312
+ layer: this.getName(),
313
+ url: initialUrl || url,
314
+ })
315
+ )
316
+ })
317
+ }
318
+
306
319
  async fetchRemoteData(force) {
307
320
  if (!this.isRemoteLayer()) return
308
- if (!this.hasDynamicData() && this.hasDataLoaded() && !force) return
321
+ if (!this.hasDynamicData() && this.isLoaded() && !force) return
309
322
  if (!this.isVisible()) return
310
- let url = this._umap.renderUrl(this.options.remoteData.url)
323
+ // Keep non proxied url for later use in Alert.
324
+ const remoteUrl = this._umap.renderUrl(this.options.remoteData.url)
325
+ let url = remoteUrl
311
326
  if (this.options.remoteData.proxy) {
312
327
  url = this._umap.proxyUrl(url, this.options.remoteData.ttl)
313
328
  }
314
- const response = await this._umap.request.get(url)
315
- if (response?.ok) {
329
+ return await this.getUrl(url, remoteUrl).then((raw) => {
316
330
  this.clear()
317
331
  return this._umap.formatter
318
- .parse(await response.text(), this.options.remoteData.format)
332
+ .parse(raw, this.options.remoteData.format)
319
333
  .then((geojson) => this.fromGeoJSON(geojson))
320
- }
334
+ .catch((error) => {
335
+ Alert.error(
336
+ translate('Cannot parse remote data for layer "{layer}" with url "{url}"', {
337
+ layer: this.getName(),
338
+ url: remoteUrl,
339
+ })
340
+ )
341
+ })
342
+ })
321
343
  }
322
344
 
323
345
  isLoaded() {
324
- return !this.createdOnServer || this._loaded
325
- }
326
-
327
- hasDataLoaded() {
328
- return this._dataloaded
346
+ return !this._needsFetch
329
347
  }
330
348
 
331
349
  backupOptions() {
@@ -444,14 +462,14 @@ export class DataLayer extends ServerStored {
444
462
  // otherwise the layer becomes uneditable.
445
463
  return this.makeFeatures(geojson, sync)
446
464
  } catch (err) {
447
- console.log('Error with DataLayer', this.id)
465
+ console.debug('Error with DataLayer', this.id)
448
466
  console.error(err)
449
467
  return []
450
468
  }
451
469
  }
452
470
 
453
471
  sortFeatures(collection) {
454
- const sortKeys = this._umap.getProperty('sortKey') || U.DEFAULT_LABEL_KEY
472
+ const sortKeys = this.getOption('sortKey') || U.DEFAULT_LABEL_KEY
455
473
  return Utils.sortFeatures(collection, sortKeys, U.lang)
456
474
  }
457
475
 
@@ -491,14 +509,14 @@ export class DataLayer extends ServerStored {
491
509
  feature = new Polygon(this._umap, this, geojson, id)
492
510
  break
493
511
  default:
494
- console.log(geojson)
512
+ console.debug(geojson)
495
513
  Alert.error(
496
514
  translate('Skipping unknown geometry.type: {type}', {
497
515
  type: geometry.type || 'undefined',
498
516
  })
499
517
  )
500
518
  }
501
- if (feature) {
519
+ if (feature && !feature.isEmpty()) {
502
520
  this.addFeature(feature)
503
521
  if (sync) feature.onCommit()
504
522
  return feature
@@ -513,6 +531,9 @@ export class DataLayer extends ServerStored {
513
531
  if (data?.length) this.isDirty = true
514
532
  return data
515
533
  })
534
+ .catch((error) => {
535
+ Alert.error(translate('Import failed: invalid data'))
536
+ })
516
537
  }
517
538
 
518
539
  readFile(f) {
@@ -542,10 +563,9 @@ export class DataLayer extends ServerStored {
542
563
 
543
564
  async importFromUrl(uri, type) {
544
565
  uri = this._umap.renderUrl(uri)
545
- const response = await this._umap.request.get(uri)
546
- if (response?.ok) {
547
- return this.importRaw(await response.text(), type)
548
- }
566
+ return await this.getUrl(uri).then((raw) => {
567
+ return this.importRaw(raw, type)
568
+ })
549
569
  }
550
570
 
551
571
  getColor() {
@@ -574,9 +594,12 @@ export class DataLayer extends ServerStored {
574
594
  })
575
595
  }
576
596
 
577
- _delete() {
578
- this.isDeleted = true
597
+ del(sync = true) {
579
598
  this.erase()
599
+ if (sync) {
600
+ this.isDeleted = true
601
+ this.sync.delete()
602
+ }
580
603
  }
581
604
 
582
605
  empty() {
@@ -604,8 +627,6 @@ export class DataLayer extends ServerStored {
604
627
  this.propagateDelete()
605
628
  this._leaflet_events_bk = this._leaflet_events
606
629
  this.clear()
607
- delete this._loaded
608
- delete this._dataloaded
609
630
  }
610
631
 
611
632
  reset() {
@@ -623,7 +644,6 @@ export class DataLayer extends ServerStored {
623
644
  this.hide()
624
645
  if (this.isRemoteLayer()) this.fetchRemoteData()
625
646
  else if (this._geojson_bk) this.fromGeoJSON(this._geojson_bk)
626
- this._loaded = true
627
647
  this.show()
628
648
  this.isDirty = false
629
649
  }
@@ -656,7 +676,7 @@ export class DataLayer extends ServerStored {
656
676
  {
657
677
  label: translate('Data is browsable'),
658
678
  handler: 'Switch',
659
- helpEntries: 'browsable',
679
+ helpEntries: ['browsable'],
660
680
  },
661
681
  ],
662
682
  [
@@ -668,20 +688,19 @@ export class DataLayer extends ServerStored {
668
688
  ],
669
689
  ]
670
690
  DomUtil.createTitle(container, translate('Layer properties'), 'icon-layers')
671
- let builder = new U.FormBuilder(this, metadataFields, {
672
- callback(e) {
673
- this._umap.onDataLayersChanged()
674
- if (e.helper.field === 'options.type') {
675
- this.edit()
676
- }
677
- },
691
+ let builder = new MutatingForm(this, metadataFields)
692
+ builder.on('set', ({ detail }) => {
693
+ this._umap.onDataLayersChanged()
694
+ if (detail.helper.field === 'options.type') {
695
+ this.edit()
696
+ }
678
697
  })
679
698
  container.appendChild(builder.build())
680
699
 
681
700
  const layerOptions = this.layer.getEditableOptions()
682
701
 
683
702
  if (layerOptions.length) {
684
- builder = new U.FormBuilder(this, layerOptions, {
703
+ builder = new MutatingForm(this, layerOptions, {
685
704
  id: 'datalayer-layer-properties',
686
705
  })
687
706
  const layerProperties = DomUtil.createFieldset(
@@ -704,7 +723,7 @@ export class DataLayer extends ServerStored {
704
723
  'options.fillOpacity',
705
724
  ]
706
725
 
707
- builder = new U.FormBuilder(this, shapeOptions, {
726
+ builder = new MutatingForm(this, shapeOptions, {
708
727
  id: 'datalayer-advanced-properties',
709
728
  })
710
729
  const shapeProperties = DomUtil.createFieldset(
@@ -719,11 +738,17 @@ export class DataLayer extends ServerStored {
719
738
  'options.zoomTo',
720
739
  'options.fromZoom',
721
740
  'options.toZoom',
741
+ 'options.sortKey',
722
742
  ]
723
743
 
724
- builder = new U.FormBuilder(this, optionsFields, {
744
+ builder = new MutatingForm(this, optionsFields, {
725
745
  id: 'datalayer-advanced-properties',
726
746
  })
747
+ builder.on('set', ({ detail }) => {
748
+ if (detail.helper.field === 'options.sortKey') {
749
+ this.reindex()
750
+ }
751
+ })
727
752
  const advancedProperties = DomUtil.createFieldset(
728
753
  container,
729
754
  translate('Advanced properties')
@@ -740,7 +765,7 @@ export class DataLayer extends ServerStored {
740
765
  'options.outlinkTarget',
741
766
  'options.interactive',
742
767
  ]
743
- builder = new U.FormBuilder(this, popupFields)
768
+ builder = new MutatingForm(this, popupFields)
744
769
  const popupFieldset = DomUtil.createFieldset(
745
770
  container,
746
771
  translate('Interaction options')
@@ -796,7 +821,7 @@ export class DataLayer extends ServerStored {
796
821
  container,
797
822
  translate('Remote data')
798
823
  )
799
- builder = new U.FormBuilder(this, remoteDataFields)
824
+ builder = new MutatingForm(this, remoteDataFields)
800
825
  remoteDataContainer.appendChild(builder.build())
801
826
  DomUtil.createButton(
802
827
  'button umap-verify',
@@ -813,44 +838,36 @@ export class DataLayer extends ServerStored {
813
838
  container,
814
839
  translate('Advanced actions')
815
840
  )
816
- const advancedButtons = DomUtil.create('div', 'button-bar half', advancedActions)
817
- const deleteButton = Utils.loadTemplate(`
818
- <button class="button" type="button">
841
+ const filename = `${Utils.slugify(this.options.name)}.geojson`
842
+ const tpl = `
843
+ <div class="button-bar half">
844
+ <button class="button" type="button" data-ref=del>
819
845
  <i class="icon icon-24 icon-delete"></i>${translate('Delete')}
820
- </button>`)
821
- deleteButton.addEventListener('click', () => {
822
- this._delete()
846
+ </button>
847
+ <button class="button" type="button" data-ref=empty hidden>
848
+ <i class="icon icon-24 icon-empty"></i>${translate('Empty')}
849
+ </button>
850
+ <button class="button" type="button" data-ref=clone>
851
+ <i class="icon icon-24 icon-clone"></i>${translate('Clone')}
852
+ </button>
853
+ <a class="button" href="${this._dataUrl()}" download="${filename}" data-ref=download hidden>
854
+ <i class="icon icon-24 icon-download"></i>${translate('Download')}
855
+ </a>
856
+ </div>
857
+ `
858
+ const [bar, { del, empty, clone, download }] = Utils.loadTemplateWithRefs(tpl)
859
+ advancedActions.appendChild(bar)
860
+ del.addEventListener('click', () => {
861
+ this.del()
823
862
  this._umap.editPanel.close()
824
863
  })
825
- advancedButtons.appendChild(deleteButton)
826
864
 
827
865
  if (!this.isRemoteLayer()) {
828
- const emptyLink = DomUtil.createButton(
829
- 'button umap-empty',
830
- advancedButtons,
831
- translate('Empty'),
832
- this.empty,
833
- this
834
- )
835
- }
836
- const cloneLink = DomUtil.createButton(
837
- 'button umap-clone',
838
- advancedButtons,
839
- translate('Clone'),
840
- function () {
841
- const datalayer = this.clone()
842
- datalayer.edit()
843
- },
844
- this
845
- )
846
- if (this.createdOnServer) {
847
- const filename = `${Utils.slugify(this.options.name)}.geojson`
848
- const download = Utils.loadTemplate(`
849
- <a class="button" href="${this._dataUrl()}" download="${filename}">
850
- <i class="icon icon-24 icon-download"></i>${translate('Download')}
851
- </a>`)
852
- advancedButtons.appendChild(download)
866
+ empty.hidden = false
867
+ empty.addEventListener('click', () => this.empty())
853
868
  }
869
+ clone.addEventListener('click', () => this.clone().edit())
870
+ if (this.createdOnServer) download.hidden = false
854
871
  const backButton = DomUtil.createButtonIcon(
855
872
  undefined,
856
873
  'icon-back',
@@ -863,6 +880,7 @@ export class DataLayer extends ServerStored {
863
880
 
864
881
  this._umap.editPanel.open({
865
882
  content: container,
883
+ highlight: 'layers',
866
884
  actions: [backButton],
867
885
  })
868
886
  }
@@ -1073,9 +1091,7 @@ export class DataLayer extends ServerStored {
1073
1091
 
1074
1092
  async save() {
1075
1093
  if (this.isDeleted) return await this.saveDelete()
1076
- if (!this.isLoaded()) {
1077
- return
1078
- }
1094
+ if (!this.isLoaded()) return
1079
1095
  const geojson = this.umapGeoJSON()
1080
1096
  const formData = new FormData()
1081
1097
  formData.append('name', this.options.name)
@@ -1137,7 +1153,6 @@ export class DataLayer extends ServerStored {
1137
1153
  this.backupOptions()
1138
1154
  this.backupData()
1139
1155
  this.connectToMap()
1140
- this._loaded = true
1141
1156
  this.redraw() // Needed for reordering features
1142
1157
  return true
1143
1158
  }
@@ -1147,14 +1162,24 @@ export class DataLayer extends ServerStored {
1147
1162
  if (this.createdOnServer) {
1148
1163
  await this._umap.server.post(this.getDeleteUrl())
1149
1164
  }
1150
- delete this._umap.datalayers[stamp(this)]
1165
+ this.commitDelete()
1151
1166
  return true
1152
1167
  }
1153
1168
 
1169
+ commitDelete() {
1170
+ delete this._umap.datalayers[stamp(this)]
1171
+ }
1172
+
1154
1173
  getName() {
1155
1174
  return this.options.name || translate('Untitled layer')
1156
1175
  }
1157
1176
 
1177
+ getPermalink() {
1178
+ return `${Utils.getBaseUrl()}?${Utils.buildQueryString({ datalayers: this.id })}${
1179
+ window.location.hash
1180
+ }`
1181
+ }
1182
+
1158
1183
  tableEdit() {
1159
1184
  if (!this.isVisible()) return
1160
1185
  const editor = new TableEditor(this._umap, this, this._leafletMap)
@@ -1221,7 +1246,7 @@ export class DataLayer extends ServerStored {
1221
1246
  this._umap.dialog
1222
1247
  .confirm(translate('Are you sure you want to delete this layer?'))
1223
1248
  .then(() => {
1224
- this._delete()
1249
+ this.del()
1225
1250
  })
1226
1251
  },
1227
1252
  this
@@ -0,0 +1,242 @@
1
+ import getClass from './fields.js'
2
+ import * as Utils from '../utils.js'
3
+ import { SCHEMA } from '../schema.js'
4
+ import { translate } from '../i18n.js'
5
+
6
+ export class Form extends Utils.WithEvents {
7
+ constructor(obj, fields, properties) {
8
+ super()
9
+ this.setProperties(properties)
10
+ this.defaultProperties = {}
11
+ this.obj = obj
12
+ this.form = Utils.loadTemplate('<form></form>')
13
+ this.setFields(fields)
14
+ if (this.properties.id) {
15
+ this.form.id = this.properties.id
16
+ }
17
+ if (this.properties.className) {
18
+ this.form.classList.add(...this.properties.className.split(' '))
19
+ }
20
+ }
21
+
22
+ setProperties(properties) {
23
+ this.properties = Object.assign({}, this.properties, properties)
24
+ }
25
+
26
+ setFields(fields) {
27
+ this.fields = fields || []
28
+ this.helpers = {}
29
+ }
30
+
31
+ build() {
32
+ this.form.innerHTML = ''
33
+ for (const definition of this.fields) {
34
+ this.buildField(this.makeField(definition))
35
+ }
36
+ return this.form
37
+ }
38
+
39
+ buildField(field) {
40
+ field.buildTemplate()
41
+ field.build()
42
+ }
43
+
44
+ makeField(field) {
45
+ // field can be either a string like "option.name" or a full definition array,
46
+ // like ['properties.tilelayer.tms', {handler: 'CheckBox', helpText: 'TMS format'}]
47
+ let properties
48
+ if (Array.isArray(field)) {
49
+ properties = field[1] || {}
50
+ field = field[0]
51
+ } else {
52
+ properties = this.defaultProperties[this.getName(field)] || {}
53
+ }
54
+ const class_ = getClass(properties.handler || 'Input')
55
+ this.helpers[field] = new class_(this, field, properties)
56
+ return this.helpers[field]
57
+ }
58
+
59
+ getter(field) {
60
+ const path = field.split('.')
61
+ let value = this.obj
62
+ for (const sub of path) {
63
+ try {
64
+ value = value[sub]
65
+ } catch {
66
+ console.debug(field)
67
+ }
68
+ }
69
+ return value
70
+ }
71
+
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
+ }
88
+ }
89
+
90
+ restoreField(field) {
91
+ const initial = this.helpers[field].initial
92
+ this.setter(field, initial)
93
+ }
94
+
95
+ getName(field) {
96
+ const fieldEls = field.split('.')
97
+ return fieldEls[fieldEls.length - 1]
98
+ }
99
+
100
+ fetchAll() {
101
+ for (const helper of Object.values(this.helpers)) {
102
+ helper.fetch()
103
+ }
104
+ }
105
+
106
+ syncAll() {
107
+ for (const helper of Object.values(this.helpers)) {
108
+ helper.sync()
109
+ }
110
+ }
111
+
112
+ onPostSync(helper) {
113
+ if (this.properties.callback) {
114
+ this.properties.callback(helper)
115
+ }
116
+ }
117
+
118
+ finish() {}
119
+
120
+ getTemplate(helper) {
121
+ return `
122
+ <div class="formbox" data-ref=container>
123
+ ${helper.getTemplate()}
124
+ <small class="help-text" data-ref=helpText></small>
125
+ </div>`
126
+ }
127
+ }
128
+
129
+ export class MutatingForm extends Form {
130
+ constructor(obj, fields, properties) {
131
+ super(obj, fields, properties)
132
+ this._umap = obj._umap || properties.umap
133
+ this.computeDefaultProperties()
134
+ // this.on('finish', this.finish)
135
+ }
136
+
137
+ computeDefaultProperties() {
138
+ const customHandlers = {
139
+ sortKey: 'PropertyInput',
140
+ easing: 'Switch',
141
+ facetKey: 'PropertyInput',
142
+ slugKey: 'PropertyInput',
143
+ labelKey: 'PropertyInput',
144
+ }
145
+ for (const [key, defaults] of Object.entries(SCHEMA)) {
146
+ 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'
155
+ } else if (properties.choices) {
156
+ const text_length = properties.choices.reduce(
157
+ (acc, [_, label]) => acc + label.length,
158
+ 0
159
+ )
160
+ // Try to be smart and use MultiChoice only
161
+ // for choices where labels are shorts…
162
+ if (text_length < 40) {
163
+ properties.handler = 'MultiChoice'
164
+ } else {
165
+ properties.handler = 'Select'
166
+ properties.selectOptions = properties.choices
167
+ }
168
+ } else {
169
+ switch (key) {
170
+ case 'color':
171
+ case 'fillColor':
172
+ properties.handler = 'ColorPicker'
173
+ break
174
+ case 'iconUrl':
175
+ properties.handler = 'IconUrl'
176
+ break
177
+ case 'licence':
178
+ properties.handler = 'LicenceChooser'
179
+ break
180
+ }
181
+ }
182
+
183
+ if (customHandlers[key]) {
184
+ properties.handler = customHandlers[key]
185
+ }
186
+ // Input uses this key for its type attribute
187
+ delete properties.type
188
+ this.defaultProperties[key] = properties
189
+ }
190
+ }
191
+
192
+ setter(field, value) {
193
+ super.setter(field, value)
194
+ this.obj.isDirty = true
195
+ if ('render' in this.obj) {
196
+ this.obj.render([field], this)
197
+ }
198
+ if ('sync' in this.obj) {
199
+ this.obj.sync.update(field, value)
200
+ }
201
+ }
202
+
203
+ getTemplate(helper) {
204
+ let template
205
+ if (helper.properties.inheritable) {
206
+ const extraClassName = this.getter(helper.field) === undefined ? ' undefined' : ''
207
+ template = `
208
+ <div class="umap-field-${helper.name} formbox inheritable${extraClassName}">
209
+ <div class="header" data-ref=header>
210
+ ${helper.getLabelTemplate()}
211
+ <span class="actions show-on-defined" data-ref=actions></span>
212
+ <span class="buttons" data-ref=buttons>
213
+ <button type="button" class="button undefine" data-ref=undefine>${translate('clear')}</button>
214
+ <button type="button" class="button define" data-ref=define>${translate('define')}</button>
215
+ </span>
216
+ </div>
217
+ <div class="show-on-defined" data-ref=container>
218
+ ${helper.getTemplate()}
219
+ <small class="help-text" data-ref=helpText></small>
220
+ </div>
221
+ </div>`
222
+ } else {
223
+ template = `
224
+ <div class="formbox umap-field-${helper.name}" data-ref=container>
225
+ ${helper.getLabelTemplate()}
226
+ ${helper.getTemplate()}
227
+ <small class="help-text" data-ref=helpText></small>
228
+ </div>`
229
+ }
230
+ return template
231
+ }
232
+
233
+ build() {
234
+ super.build()
235
+ this._umap.help.parse(this.form)
236
+ return this.form
237
+ }
238
+
239
+ finish(helper) {
240
+ helper.input?.blur()
241
+ }
242
+ }