umap-project 2.3.1__py3-none-any.whl → 2.4.0b0__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.
Files changed (210) hide show
  1. umap/.DS_Store +0 -0
  2. umap/__init__.py +1 -1
  3. umap/locale/en/LC_MESSAGES/django.po +81 -31
  4. umap/locale/fr/LC_MESSAGES/django.mo +0 -0
  5. umap/locale/fr/LC_MESSAGES/django.po +109 -59
  6. umap/management/commands/run_websocket_server.py +23 -0
  7. umap/models.py +6 -1
  8. umap/settings/base.py +11 -3
  9. umap/static/.DS_Store +0 -0
  10. umap/static/umap/.DS_Store +0 -0
  11. umap/static/umap/base.css +53 -162
  12. umap/static/umap/content.css +3 -2
  13. umap/static/umap/css/dialog.css +18 -0
  14. umap/static/umap/css/icon.css +8 -0
  15. umap/static/umap/css/importers.css +44 -0
  16. umap/static/umap/css/panel.css +19 -57
  17. umap/static/umap/css/tooltip.css +59 -0
  18. umap/static/umap/css/window.css +35 -0
  19. umap/static/umap/favicons/.DS_Store +0 -0
  20. umap/static/umap/fonts/.DS_Store +0 -0
  21. umap/static/umap/img/.DS_Store +0 -0
  22. umap/static/umap/img/alert-icon-error.svg +8 -0
  23. umap/static/umap/img/alert-icon-info.svg +4 -0
  24. umap/static/umap/img/alert-icon-success.svg +3 -0
  25. umap/static/umap/img/icon-external-link.svg +3 -0
  26. umap/static/umap/img/importers/communesfr.svg +5 -0
  27. umap/static/umap/img/importers/datasets.svg +13 -0
  28. umap/static/umap/img/importers/geodatamine.svg +10 -0
  29. umap/static/umap/img/importers/overpass.svg +7 -0
  30. umap/static/umap/img/importers/random.svg +18 -0
  31. umap/static/umap/img/importers/random1.svg +4 -0
  32. umap/static/umap/img/importers/random2.svg +4 -0
  33. umap/static/umap/img/source/.DS_Store +0 -0
  34. umap/static/umap/js/components/alerts/alert.css +160 -0
  35. umap/static/umap/js/components/alerts/alert.js +169 -0
  36. umap/static/umap/js/components/base.js +54 -0
  37. umap/static/umap/js/modules/autocomplete.js +347 -0
  38. umap/static/umap/js/modules/browser.js +1 -1
  39. umap/static/umap/js/modules/caption.js +4 -3
  40. umap/static/umap/js/modules/global.js +36 -12
  41. umap/static/umap/js/modules/help.js +255 -0
  42. umap/static/umap/js/modules/importer.js +280 -0
  43. umap/static/umap/js/modules/importers/communesfr.js +44 -0
  44. umap/static/umap/js/modules/importers/datasets.js +41 -0
  45. umap/static/umap/js/modules/importers/geodatamine.js +95 -0
  46. umap/static/umap/js/modules/importers/overpass.js +84 -0
  47. umap/static/umap/js/modules/request.js +12 -14
  48. umap/static/umap/js/modules/rules.js +241 -0
  49. umap/static/umap/js/modules/schema.js +63 -14
  50. umap/static/umap/js/modules/sync/engine.js +93 -0
  51. umap/static/umap/js/modules/sync/updaters.js +109 -0
  52. umap/static/umap/js/modules/sync/websocket.js +25 -0
  53. umap/static/umap/js/modules/ui/dialog.js +52 -0
  54. umap/static/umap/js/modules/{panel.js → ui/panel.js} +25 -14
  55. umap/static/umap/js/modules/ui/tooltip.js +116 -0
  56. umap/static/umap/js/modules/utils.js +25 -18
  57. umap/static/umap/js/umap.controls.js +13 -14
  58. umap/static/umap/js/umap.core.js +1 -324
  59. umap/static/umap/js/umap.features.js +67 -27
  60. umap/static/umap/js/umap.forms.js +9 -13
  61. umap/static/umap/js/umap.js +220 -180
  62. umap/static/umap/js/umap.layer.js +142 -74
  63. umap/static/umap/js/umap.permissions.js +5 -9
  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 +51 -16
  86. umap/static/umap/locale/en.json +51 -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 +52 -17
  99. umap/static/umap/locale/fr.json +52 -17
  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 +27 -41
  165. umap/static/umap/unittests/sync.js +105 -0
  166. umap/static/umap/unittests/utils.js +76 -34
  167. umap/static/umap/vars.css +18 -1
  168. umap/static/umap/vendors/dompurify/purify.es.js +5 -59
  169. umap/static/umap/vendors/dompurify/purify.es.mjs.map +1 -1
  170. umap/templates/umap/components/alerts/alert.html +89 -0
  171. umap/templates/umap/content.html +4 -3
  172. umap/templates/umap/css.html +4 -0
  173. umap/templates/umap/home.html +3 -0
  174. umap/templates/umap/js.html +0 -3
  175. umap/templates/umap/map_init.html +2 -8
  176. umap/templates/umap/messages.html +9 -11
  177. umap/templates/umap/search.html +3 -0
  178. umap/tests/.DS_Store +0 -0
  179. umap/tests/base.py +2 -0
  180. umap/tests/integration/.DS_Store +0 -0
  181. umap/tests/integration/conftest.py +30 -0
  182. umap/tests/integration/test_anonymous_owned_map.py +8 -13
  183. umap/tests/integration/test_browser.py +1 -1
  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 +4 -4
  188. umap/tests/integration/test_edit_map.py +1 -1
  189. umap/tests/integration/test_facets_browser.py +3 -3
  190. umap/tests/integration/test_import.py +138 -49
  191. umap/tests/integration/test_map.py +2 -2
  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_websocket_sync.py +283 -0
  197. umap/tests/settings.py +5 -0
  198. umap/tests/test_datalayer_views.py +0 -1
  199. umap/tests/test_views.py +53 -0
  200. umap/urls.py +5 -0
  201. umap/views.py +40 -11
  202. umap/websocket_server.py +92 -0
  203. {umap_project-2.3.1.dist-info → umap_project-2.4.0b0.dist-info}/METADATA +11 -9
  204. {umap_project-2.3.1.dist-info → umap_project-2.4.0b0.dist-info}/RECORD +207 -164
  205. {umap_project-2.3.1.dist-info → umap_project-2.4.0b0.dist-info}/WHEEL +1 -1
  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.1.dist-info → umap_project-2.4.0b0.dist-info}/entry_points.txt +0 -0
  210. {umap_project-2.3.1.dist-info → umap_project-2.4.0b0.dist-info}/licenses/LICENSE +0 -0
@@ -20,9 +20,6 @@ L.Map.mergeOptions({
20
20
  enablePolygonDraw: true,
21
21
  enablePolylineDraw: true,
22
22
  limitBounds: {},
23
- importPresets: [
24
- // {url: 'http://localhost:8019/en/datalayer/1502/', label: 'Simplified World Countries', format: 'geojson'}
25
- ],
26
23
  slideshow: {},
27
24
  clickable: true,
28
25
  permissions: {},
@@ -33,6 +30,8 @@ U.Map = L.Map.extend({
33
30
  includes: [ControlsMixin],
34
31
 
35
32
  initialize: function (el, geojson) {
33
+ this.sync_engine = new U.SyncEngine(this)
34
+ this.sync = this.sync_engine.proxy(this)
36
35
  // Locale name (pt_PT, en_US…)
37
36
  // To be used for Django localization
38
37
  if (geojson.properties.locale) L.setLocale(geojson.properties.locale)
@@ -57,15 +56,16 @@ U.Map = L.Map.extend({
57
56
  this.urls = new U.URLs(this.options.urls)
58
57
 
59
58
  this.panel = new U.Panel(this)
59
+ this.tooltip = new U.Tooltip(this._controlContainer)
60
+ this.dialog = new U.Dialog(this._controlContainer)
60
61
  if (this.hasEditMode()) {
61
62
  this.editPanel = new U.EditPanel(this)
62
63
  this.fullPanel = new U.FullPanel(this)
63
64
  }
64
- this.ui = new U.UI(this._container)
65
- this.ui.on('dataloading', (e) => this.fire('dataloading', e))
66
- this.ui.on('dataload', (e) => this.fire('dataload', e))
67
- this.server = new U.ServerRequest(this.ui)
68
- this.request = new U.Request(this.ui)
65
+ L.DomEvent.on(document.body, 'dataloading', (e) => this.fire('dataloading', e))
66
+ L.DomEvent.on(document.body, 'dataload', (e) => this.fire('dataload', e))
67
+ this.server = new U.ServerRequest()
68
+ this.request = new U.Request()
69
69
 
70
70
  this.initLoader()
71
71
  this.name = this.options.name
@@ -205,6 +205,13 @@ U.Map = L.Map.extend({
205
205
  this.editTools = new U.Editable(this)
206
206
  this.renderEditToolbar()
207
207
  }
208
+ if (!U.Utils.isObject(this.options.overlay)) {
209
+ this.options.overlay = {}
210
+ }
211
+ if (!U.Utils.isObject(this.options.tilelayer)) {
212
+ this.options.tilelayer = {}
213
+ }
214
+
208
215
  this.initShortcuts()
209
216
  this.onceDataLoaded(function () {
210
217
  const slug = L.Util.queryString('feature')
@@ -248,6 +255,25 @@ U.Map = L.Map.extend({
248
255
  this.on('click contextmenu.show', this.closeInplaceToolbar)
249
256
  },
250
257
 
258
+ initSyncEngine: async function () {
259
+ if (this.options.websocketEnabled == false) return
260
+ if (this.options.syncEnabled != true) {
261
+ this.sync.stop()
262
+ } else {
263
+ const ws_token_uri = this.urls.get('map_websocket_auth_token', {
264
+ map_id: this.options.umap_id,
265
+ })
266
+ await this.sync.authenticate(ws_token_uri, this.options.websocketURI, this.server)
267
+ }
268
+ },
269
+
270
+ getSyncMetadata: function () {
271
+ return {
272
+ engine: this.sync,
273
+ subject: 'map',
274
+ }
275
+ },
276
+
251
277
  render: function (fields) {
252
278
  let impacts = U.Utils.getImpactsFromSchema(fields)
253
279
 
@@ -271,6 +297,8 @@ U.Map = L.Map.extend({
271
297
  case 'bounds':
272
298
  this.handleLimitBounds()
273
299
  break
300
+ case 'sync':
301
+ this.initSyncEngine()
274
302
  }
275
303
  }
276
304
  },
@@ -359,7 +387,7 @@ U.Map = L.Map.extend({
359
387
  icon: 'umap-fake-class',
360
388
  iconLoading: 'umap-fake-class',
361
389
  flyTo: this.options.easing,
362
- onLocationError: (err) => this.ui.alert({ content: err.message }),
390
+ onLocationError: (err) => U.Alert.error(err.message),
363
391
  })
364
392
  this._controls.fullscreen = new L.Control.Fullscreen({
365
393
  title: { false: L._('View Fullscreen'), true: L._('Exit Fullscreen') },
@@ -388,11 +416,14 @@ U.Map = L.Map.extend({
388
416
  this.importer = new U.Importer(this)
389
417
  this.drop = new U.DropControl(this)
390
418
  this.share = new U.Share(this)
419
+ this.rules = new U.Rules(this)
391
420
  this._controls.tilelayers = new U.TileLayerControl(this)
392
421
  },
393
422
 
394
423
  renderControls: function () {
395
- const hasSlideshow = Boolean(this.options.slideshow && this.options.slideshow.active)
424
+ const hasSlideshow = Boolean(
425
+ this.options.slideshow && this.options.slideshow.active
426
+ )
396
427
  const barEnabled = this.options.captionBar || hasSlideshow
397
428
  document.body.classList.toggle('umap-caption-bar-enabled', barEnabled)
398
429
  document.body.classList.toggle('umap-slideshow-enabled', hasSlideshow)
@@ -514,73 +545,80 @@ U.Map = L.Map.extend({
514
545
 
515
546
  initShortcuts: function () {
516
547
  const globalShortcuts = function (e) {
517
- const key = e.keyCode
518
- const hasModifier = (e.ctrlKey || e.metaKey) && !e.shiftKey
519
-
520
- /* Generic shortcuts */
521
- if (key === U.Keys.F && hasModifier) {
522
- L.DomEvent.stop(e)
523
- this.search()
524
- } else if (e.keyCode === U.Keys.ESC) {
525
- if (this.help.visible()) {
526
- this.help.hide()
527
- } else {
528
- this.panel.close()
548
+ if (e.key === 'Escape') {
549
+ if (this.dialog.visible) {
550
+ this.dialog.close()
551
+ } else if (this.importer.dialog.visible) {
552
+ this.importer.dialog.close()
553
+ } else if (this.editEnabled && this.editTools.drawing()) {
554
+ this.editTools.stopDrawing()
555
+ } else if (this.measureTools.enabled()) {
556
+ this.measureTools.stopDrawing()
557
+ } else if (this.editPanel?.isOpen()) {
529
558
  this.editPanel?.close()
559
+ } else if (this.fullPanel?.isOpen()) {
530
560
  this.fullPanel?.close()
561
+ } else if (this.panel.isOpen()) {
562
+ this.panel.close()
531
563
  }
532
564
  }
533
565
 
534
- if (!this.hasEditMode()) return
566
+ // From now on, only ctrl/meta shortcut
567
+ if (!(e.ctrlKey || e.metaKey) || e.shiftKey) return
535
568
 
536
- /* Edit mode only shortcuts */
537
- if (key === U.Keys.E && hasModifier && !this.editEnabled) {
538
- L.DomEvent.stop(e)
539
- this.enableEdit()
540
- } else if (key === U.Keys.E && hasModifier && this.editEnabled && !this.isDirty) {
569
+ if (e.key === 'f') {
541
570
  L.DomEvent.stop(e)
542
- this.disableEdit()
571
+ this.search()
543
572
  }
544
- if (key === U.Keys.S && hasModifier) {
545
- L.DomEvent.stop(e)
546
- if (this.isDirty) {
547
- this.save()
573
+
574
+ /* Edit mode only shortcuts */
575
+ if (!this.hasEditMode()) return
576
+
577
+ // Edit mode Off
578
+ if (!this.editEnabled) {
579
+ switch (e.key) {
580
+ case 'e':
581
+ L.DomEvent.stop(e)
582
+ this.enableEdit()
583
+ break
548
584
  }
585
+ return
549
586
  }
550
- if (key === U.Keys.Z && hasModifier && this.isDirty) {
551
- L.DomEvent.stop(e)
552
- this.askForReset()
553
- }
554
- if (key === U.Keys.M && hasModifier && this.editEnabled) {
555
- L.DomEvent.stop(e)
556
- this.editTools.startMarker()
557
- }
558
- if (key === U.Keys.P && hasModifier && this.editEnabled) {
559
- L.DomEvent.stop(e)
560
- this.editTools.startPolygon()
561
- }
562
- if (key === U.Keys.L && hasModifier && this.editEnabled) {
563
- L.DomEvent.stop(e)
564
- this.editTools.startPolyline()
565
- }
566
- if (key === U.Keys.I && hasModifier && this.editEnabled) {
567
- L.DomEvent.stop(e)
568
- this.importer.open()
569
- }
570
- if (key === U.Keys.O && hasModifier && this.editEnabled) {
571
- L.DomEvent.stop(e)
572
- this.importer.openFiles()
573
- }
574
- if (key === U.Keys.H && hasModifier && this.editEnabled) {
575
- L.DomEvent.stop(e)
576
- this.help.show('edit')
577
- }
578
- if (e.keyCode === U.Keys.ESC) {
579
- if (this.editEnabled && this.editTools.drawing()) {
580
- this.editTools.stopDrawing()
581
- }
582
- if (this.measureTools.enabled()) this.measureTools.stopDrawing()
587
+
588
+ // Edit mode on
589
+ let used = true
590
+ switch (e.key) {
591
+ case 'e':
592
+ if (!this.isDirty) this.disableEdit()
593
+ break
594
+ case 's':
595
+ if (this.isDirty) this.save()
596
+ break
597
+ case 'z':
598
+ if (this.isDirty) this.askForReset()
599
+ break
600
+ case 'm':
601
+ this.editTools.startMarker()
602
+ break
603
+ case 'p':
604
+ this.editTools.startPolygon()
605
+ break
606
+ case 'l':
607
+ this.editTools.startPolyline()
608
+ break
609
+ case 'i':
610
+ this.importer.open()
611
+ break
612
+ case 'o':
613
+ this.importer.openFiles()
614
+ break
615
+ case 'h':
616
+ this.help.show('edit')
617
+ break
618
+ default:
619
+ used = false
583
620
  }
621
+ if (used) L.DomEvent.stop(e)
584
622
  }
585
623
  L.DomEvent.addListener(document, 'keydown', globalShortcuts, this)
586
624
  },
@@ -641,10 +679,7 @@ U.Map = L.Map.extend({
641
679
  } catch (e) {
642
680
  console.error(e)
643
681
  this.removeLayer(tilelayer)
644
- this.ui.alert({
645
- content: `${L._('Error in the tilelayer URL')}: ${tilelayer._url}`,
646
- level: 'error',
647
- })
682
+ U.Alert.error(`${L._('Error in the tilelayer URL')}: ${tilelayer._url}`)
648
683
  // Users can put tilelayer URLs by hand, and if they add wrong {variable},
649
684
  // Leaflet throw an error, and then the map is no more editable
650
685
  }
@@ -676,10 +711,7 @@ U.Map = L.Map.extend({
676
711
  } catch (e) {
677
712
  this.removeLayer(overlay)
678
713
  console.error(e)
679
- this.ui.alert({
680
- content: `${L._('Error in the overlay URL')}: ${overlay._url}`,
681
- level: 'error',
682
- })
714
+ U.Alert.error(`${L._('Error in the overlay URL')}: ${overlay._url}`)
683
715
  }
684
716
  },
685
717
 
@@ -770,11 +802,14 @@ U.Map = L.Map.extend({
770
802
  return L.Map.prototype.setMaxBounds.call(this, bounds)
771
803
  },
772
804
 
773
- createDataLayer: function (datalayer) {
774
- datalayer = datalayer || {
775
- name: `${L._('Layer')} ${this.datalayers_index.length + 1}`,
805
+ createDataLayer: function (options = {}, sync) {
806
+ options.name = options.name || `${L._('Layer')} ${this.datalayers_index.length + 1}`
807
+ const datalayer = new U.DataLayer(this, options, sync)
808
+
809
+ if (sync !== false) {
810
+ datalayer.sync.upsert(datalayer.options)
776
811
  }
777
- return new U.DataLayer(this, datalayer)
812
+ return datalayer
778
813
  },
779
814
 
780
815
  newDataLayer: function () {
@@ -786,24 +821,25 @@ U.Map = L.Map.extend({
786
821
  return U.SCHEMA[option] && U.SCHEMA[option].default
787
822
  },
788
823
 
789
- getOption: function (option) {
824
+ getOption: function (option, feature) {
825
+ if (feature) {
826
+ const value = this.rules.getOption(option, feature)
827
+ if (value !== undefined) return value
828
+ }
790
829
  if (U.Utils.usableOption(this.options, option)) return this.options[option]
791
830
  return this.getDefaultOption(option)
792
831
  },
793
832
 
794
- updateExtent: function () {
833
+ setCenterAndZoom: function () {
834
+ this._setCenterAndZoom()
835
+ U.Alert.success(L._('The zoom and center have been modified.'))
836
+ },
837
+
838
+ _setCenterAndZoom: function () {
795
839
  this.options.center = this.getCenter()
796
840
  this.options.zoom = this.getZoom()
797
841
  this.isDirty = true
798
842
  this._default_extent = false
799
- if (this.options.umap_id) {
800
- // We do not want an extra message during the map creation
801
- // to avoid the double notification/alert.
802
- this.ui.alert({
803
- content: L._('The zoom and center have been modified.'),
804
- level: 'info',
805
- })
806
- }
807
843
  },
808
844
 
809
845
  updateTileLayers: function () {
@@ -842,12 +878,11 @@ U.Map = L.Map.extend({
842
878
  processFileToImport: function (file, layer, type) {
843
879
  type = type || U.Utils.detectFileType(file)
844
880
  if (!type) {
845
- this.ui.alert({
846
- content: L._('Unable to detect format of file {filename}', {
881
+ U.Alert.error(
882
+ L._('Unable to detect format of file {filename}', {
847
883
  filename: file.name,
848
- }),
849
- level: 'error',
850
- })
884
+ })
885
+ )
851
886
  return
852
887
  }
853
888
  if (type === 'umap') {
@@ -858,6 +893,13 @@ U.Map = L.Map.extend({
858
893
  }
859
894
  },
860
895
 
896
+ importFromUrl: async function (uri) {
897
+ const response = await this.request.get(uri)
898
+ if (response && response.ok) {
899
+ this.importRaw(await response.text())
900
+ }
901
+ },
902
+
861
903
  importRaw: function (rawData) {
862
904
  const importedData = JSON.parse(rawData)
863
905
 
@@ -873,7 +915,11 @@ U.Map = L.Map.extend({
873
915
  if (importedData.geometry) this.options.center = this.latLng(importedData.geometry)
874
916
  const self = this
875
917
  importedData.layers.forEach((geojson) => {
876
- delete geojson._umap_options['id'] // Never trust an id at this stage
918
+ if (!geojson._umap_options && geojson._storage) {
919
+ geojson._umap_options = geojson._storage
920
+ delete geojson._storage
921
+ }
922
+ delete geojson._umap_options?.id // Never trust an id at this stage
877
923
  const dataLayer = self.createDataLayer(geojson._umap_options)
878
924
  dataLayer.fromUmapGeoJSON(geojson)
879
925
  })
@@ -899,10 +945,7 @@ U.Map = L.Map.extend({
899
945
  self.importRaw(rawData)
900
946
  } catch (e) {
901
947
  console.error('Error importing data', e)
902
- self.ui.alert({
903
- content: L._('Invalid umap data in {filename}', { filename: file.name }),
904
- level: 'error',
905
- })
948
+ U.Alert.error(L._('Invalid umap data in {filename}', { filename: file.name }))
906
949
  }
907
950
  }
908
951
  },
@@ -997,6 +1040,7 @@ U.Map = L.Map.extend({
997
1040
  },
998
1041
 
999
1042
  saveSelf: async function () {
1043
+ this.rules.commit()
1000
1044
  const geojson = {
1001
1045
  type: 'Feature',
1002
1046
  geometry: this.geometry(),
@@ -1007,65 +1051,56 @@ U.Map = L.Map.extend({
1007
1051
  formData.append('center', JSON.stringify(this.geometry()))
1008
1052
  formData.append('settings', JSON.stringify(geojson))
1009
1053
  const uri = this.urls.get('map_save', { map_id: this.options.umap_id })
1010
- const [data, response, error] = await this.server.post(uri, {}, formData)
1054
+ const [data, _, error] = await this.server.post(uri, {}, formData)
1011
1055
  // FIXME: login_required response will not be an error, so it will not
1012
1056
  // stop code while it should
1013
- if (!error) {
1014
- let duration = 3000,
1015
- alert = { content: L._('Map has been saved!'), level: 'info' }
1016
- if (!this.options.umap_id) {
1017
- alert.content = L._('Congratulations, your map has been created!')
1018
- this.options.umap_id = data.id
1019
- this.permissions.setOptions(data.permissions)
1020
- this.permissions.commit()
1021
- if (data.permissions && data.permissions.anonymous_edit_url) {
1022
- alert.duration = Infinity
1023
- alert.content =
1024
- L._(
1025
- 'Your map has been created! As you are not logged in, here is your secret link to edit the map, please keep it safe:'
1026
- ) + `<br>${data.permissions.anonymous_edit_url}`
1027
-
1028
- alert.actions = [
1029
- {
1030
- label: L._('Copy link'),
1031
- callback: () => {
1032
- L.Util.copyToClipboard(data.permissions.anonymous_edit_url)
1033
- this.ui.alert({
1034
- content: L._('Secret edit link copied to clipboard!'),
1035
- level: 'info',
1036
- })
1037
- },
1038
- callbackContext: this,
1039
- },
1040
- ]
1041
- if (this.options.urls.map_send_edit_link) {
1042
- alert.actions.push({
1043
- label: L._('Send me the link'),
1044
- input: L._('Email'),
1045
- callback: this.sendEditLink,
1046
- callbackContext: this,
1047
- })
1048
- }
1049
- }
1050
- } else if (!this.permissions.isDirty) {
1057
+ if (error) {
1058
+ return
1059
+ }
1060
+
1061
+ if (!this.options.umap_id) {
1062
+ this.options.umap_id = data.id
1063
+ this.permissions.setOptions(data.permissions)
1064
+ this.permissions.commit()
1065
+ if (data?.permissions?.anonymous_edit_url) {
1066
+ this.once('saved', () => {
1067
+ U.AlertCreation.info(
1068
+ L._('Your map has been created with an anonymous account!'),
1069
+ Number.Infinity,
1070
+ data.permissions.anonymous_edit_url,
1071
+ this.options.urls.map_send_edit_link
1072
+ ? this.sendEditLinkEmail.bind(this)
1073
+ : null
1074
+ )
1075
+ })
1076
+ } else {
1077
+ this.once('saved', () => {
1078
+ U.Alert.success(L._('Congratulations, your map has been created!'))
1079
+ })
1080
+ }
1081
+ } else {
1082
+ if (!this.permissions.isDirty) {
1051
1083
  // Do not override local changes to permissions,
1052
1084
  // but update in case some other editors changed them in the meantime.
1053
1085
  this.permissions.setOptions(data.permissions)
1054
1086
  this.permissions.commit()
1055
1087
  }
1056
- // Update URL in case the name has changed.
1057
- if (history && history.pushState)
1058
- history.pushState({}, this.options.name, data.url)
1059
- else window.location = data.url
1060
- alert.content = data.info || alert.content
1061
- this.once('saved', () => this.ui.alert(alert))
1062
- this.permissions.save()
1088
+ this.once('saved', () => {
1089
+ U.Alert.success(data.info || L._('Map has been saved!'))
1090
+ })
1091
+ }
1092
+ // Update URL in case the name has changed.
1093
+ if (history?.pushState) {
1094
+ history.pushState({}, this.options.name, data.url)
1095
+ } else {
1096
+ window.location = data.url
1063
1097
  }
1098
+ this.permissions.save()
1064
1099
  },
1065
1100
 
1066
1101
  save: function () {
1067
1102
  if (!this.isDirty) return
1068
- if (this._default_extent) this.updateExtent()
1103
+ if (this._default_extent) this._setCenterAndZoom()
1069
1104
  this.backup()
1070
1105
  this.once('saved', () => {
1071
1106
  this.isDirty = false
@@ -1078,33 +1113,20 @@ U.Map = L.Map.extend({
1078
1113
  }
1079
1114
  },
1080
1115
 
1081
- sendEditLink: async function () {
1082
- const input = this.ui._alert.querySelector('input')
1083
- const email = input.value
1084
-
1085
- const formData = new FormData()
1086
- formData.append('email', email)
1087
-
1088
- const url = this.urls.get('map_send_edit_link', { map_id: this.options.umap_id })
1089
- await this.server.post(url, {}, formData)
1090
- },
1091
-
1092
1116
  star: async function () {
1093
- if (!this.options.umap_id)
1094
- return this.ui.alert({
1095
- content: L._('Please save the map first'),
1096
- level: 'error',
1097
- })
1117
+ if (!this.options.umap_id) {
1118
+ return U.Alert.error(L._('Please save the map first'))
1119
+ }
1098
1120
  const url = this.urls.get('map_star', { map_id: this.options.umap_id })
1099
1121
  const [data, response, error] = await this.server.post(url)
1100
- if (!error) {
1101
- this.options.starred = data.starred
1102
- let msg = data.starred
1103
- ? L._('Map has been starred')
1104
- : L._('Map has been unstarred')
1105
- this.ui.alert({ content: msg, level: 'info' })
1106
- this.renderControls()
1122
+ if (error) {
1123
+ return
1107
1124
  }
1125
+ this.options.starred = data.starred
1126
+ U.Alert.success(
1127
+ data.starred ? L._('Map has been starred') : L._('Map has been unstarred')
1128
+ )
1129
+ this.renderControls()
1108
1130
  },
1109
1131
 
1110
1132
  geometry: function () {
@@ -1274,9 +1296,6 @@ U.Map = L.Map.extend({
1274
1296
  },
1275
1297
 
1276
1298
  _editTilelayer: function (container) {
1277
- if (!U.Utils.isObject(this.options.tilelayer)) {
1278
- this.options.tilelayer = {}
1279
- }
1280
1299
  const tilelayerFields = [
1281
1300
  [
1282
1301
  'options.tilelayer.name',
@@ -1324,9 +1343,6 @@ U.Map = L.Map.extend({
1324
1343
  },
1325
1344
 
1326
1345
  _editOverlay: function (container) {
1327
- if (!U.Utils.isObject(this.options.overlay)) {
1328
- this.options.overlay = {}
1329
- }
1330
1346
  const overlayFields = [
1331
1347
  [
1332
1348
  'options.overlay.url_template',
@@ -1408,6 +1424,8 @@ U.Map = L.Map.extend({
1408
1424
  this.options.limitBounds.north = L.Util.formatNum(bounds.getNorth())
1409
1425
  this.options.limitBounds.east = L.Util.formatNum(bounds.getEast())
1410
1426
  boundsBuilder.fetchAll()
1427
+
1428
+ this.sync.update(this, 'options.limitBounds', this.options.limitBounds)
1411
1429
  this.isDirty = true
1412
1430
  this.handleLimitBounds()
1413
1431
  },
@@ -1463,6 +1481,12 @@ U.Map = L.Map.extend({
1463
1481
  slideshow.appendChild(slideshowBuilder.build())
1464
1482
  },
1465
1483
 
1484
+ _editSync: function (container) {
1485
+ const sync = L.DomUtil.createFieldset(container, L._('Real-time collaboration'))
1486
+ const builder = new U.FormBuilder(this, ['options.syncEnabled'])
1487
+ sync.appendChild(builder.build())
1488
+ },
1489
+
1466
1490
  _advancedActions: function (container) {
1467
1491
  const advancedActions = L.DomUtil.createFieldset(container, L._('Advanced actions'))
1468
1492
  const advancedButtons = L.DomUtil.create('div', 'button-bar half', advancedActions)
@@ -1508,9 +1532,10 @@ U.Map = L.Map.extend({
1508
1532
  editCaption: function () {
1509
1533
  if (!this.editEnabled) return
1510
1534
  if (this.options.editMode !== 'advanced') return
1511
- const container = L.DomUtil.create('div', 'umap-edit-container'),
1512
- metadataFields = ['options.name', 'options.description'],
1513
- title = L.DomUtil.create('h3', '', container)
1535
+ const container = L.DomUtil.create('div', 'umap-edit-container')
1536
+ const metadataFields = ['options.name', 'options.description']
1537
+
1538
+ const title = L.DomUtil.create('h3', '', container)
1514
1539
  title.textContent = L._('Edit map details')
1515
1540
  const builder = new U.FormBuilder(this, metadataFields, {
1516
1541
  className: 'map-metadata',
@@ -1540,10 +1565,14 @@ U.Map = L.Map.extend({
1540
1565
  this._editShapeProperties(container)
1541
1566
  this._editDefaultProperties(container)
1542
1567
  this._editInteractionsProperties(container)
1568
+ this.rules.edit(container)
1543
1569
  this._editTilelayer(container)
1544
1570
  this._editOverlay(container)
1545
1571
  this._editBounds(container)
1546
1572
  this._editSlideshow(container)
1573
+ if (this.options.websocketEnabled) {
1574
+ this._editSync(container)
1575
+ }
1547
1576
  this._advancedActions(container)
1548
1577
 
1549
1578
  this.editPanel.open({ content: container, className: 'dark' })
@@ -1554,6 +1583,7 @@ U.Map = L.Map.extend({
1554
1583
  this.editEnabled = true
1555
1584
  this.drop.enable()
1556
1585
  this.fire('edit:enabled')
1586
+ this.initSyncEngine()
1557
1587
  },
1558
1588
 
1559
1589
  disableEdit: function () {
@@ -1565,6 +1595,7 @@ U.Map = L.Map.extend({
1565
1595
  this.fire('edit:disabled')
1566
1596
  this.editPanel.close()
1567
1597
  this.fullPanel.close()
1598
+ this.sync.stop()
1568
1599
  },
1569
1600
 
1570
1601
  hasEditMode: function () {
@@ -1588,7 +1619,7 @@ U.Map = L.Map.extend({
1588
1619
  L.DomUtil.createButton(
1589
1620
  'umap-about-link flat',
1590
1621
  container,
1591
- L._('About'),
1622
+ L._('Open caption'),
1592
1623
  this.openCaption,
1593
1624
  this
1594
1625
  )
@@ -1739,7 +1770,7 @@ U.Map = L.Map.extend({
1739
1770
  items.push(
1740
1771
  '-',
1741
1772
  {
1742
- text: L._('See layers'),
1773
+ text: L._('Open browser'),
1743
1774
  callback: () => this.openBrowser('layers'),
1744
1775
  },
1745
1776
  {
@@ -1755,7 +1786,7 @@ U.Map = L.Map.extend({
1755
1786
  }
1756
1787
  items.push(
1757
1788
  {
1758
- text: L._('About'),
1789
+ text: L._('Open caption'),
1759
1790
  callback: this.openCaption,
1760
1791
  },
1761
1792
  {
@@ -1853,4 +1884,13 @@ U.Map = L.Map.extend({
1853
1884
  })
1854
1885
  return bounds
1855
1886
  },
1887
+
1888
+ sendEditLinkEmail: async function (formData) {
1889
+ const sendLink =
1890
+ this.options.urls.map_send_edit_link &&
1891
+ this.urls.get('map_send_edit_link', {
1892
+ map_id: this.options.umap_id,
1893
+ })
1894
+ await this.server.post(sendLink, {}, formData)
1895
+ },
1856
1896
  })