umap-project 2.9.3__py3-none-any.whl → 3.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of umap-project might be problematic. Click here for more details.

Files changed (217) hide show
  1. umap/__init__.py +1 -1
  2. umap/context_processors.py +1 -0
  3. umap/forms.py +1 -2
  4. umap/locale/de/LC_MESSAGES/django.mo +0 -0
  5. umap/locale/de/LC_MESSAGES/django.po +218 -96
  6. umap/locale/en/LC_MESSAGES/django.po +128 -52
  7. umap/locale/fr/LC_MESSAGES/django.mo +0 -0
  8. umap/locale/fr/LC_MESSAGES/django.po +128 -52
  9. umap/locale/hu/LC_MESSAGES/django.mo +0 -0
  10. umap/locale/hu/LC_MESSAGES/django.po +209 -88
  11. umap/locale/is/LC_MESSAGES/django.mo +0 -0
  12. umap/locale/is/LC_MESSAGES/django.po +296 -175
  13. umap/migrations/0027_map_tags.py +23 -0
  14. umap/models.py +13 -2
  15. umap/settings/base.py +23 -5
  16. umap/static/umap/base.css +41 -8
  17. umap/static/umap/content.css +72 -37
  18. umap/static/umap/css/bar.css +43 -21
  19. umap/static/umap/css/dialog.css +4 -1
  20. umap/static/umap/css/form.css +40 -27
  21. umap/static/umap/css/icon.css +11 -1
  22. umap/static/umap/css/importers.css +7 -0
  23. umap/static/umap/img/16-white.svg +23 -2
  24. umap/static/umap/img/16.svg +1 -1
  25. umap/static/umap/img/24.svg +4 -4
  26. umap/static/umap/img/home.svg +7 -0
  27. umap/static/umap/img/importers/banfr.svg +1 -0
  28. umap/static/umap/img/marker.svg +2 -5
  29. umap/static/umap/img/source/16-white.svg +24 -3
  30. umap/static/umap/img/source/16.svg +1 -1
  31. umap/static/umap/img/source/24.svg +5 -5
  32. umap/static/umap/img/target.svg +1 -0
  33. umap/static/umap/js/components/alerts/alert.js +0 -1
  34. umap/static/umap/js/modules/browser.js +4 -4
  35. umap/static/umap/js/modules/caption.js +1 -1
  36. umap/static/umap/js/modules/data/features.js +25 -25
  37. umap/static/umap/js/modules/data/layer.js +91 -97
  38. umap/static/umap/js/modules/facets.js +9 -5
  39. umap/static/umap/js/modules/form/builder.js +19 -27
  40. umap/static/umap/js/modules/form/fields.js +40 -14
  41. umap/static/umap/js/modules/formatter.js +1 -1
  42. umap/static/umap/js/modules/global.js +9 -5
  43. umap/static/umap/js/modules/help.js +18 -5
  44. umap/static/umap/js/modules/importer.js +5 -2
  45. umap/static/umap/js/modules/importers/banfr.js +93 -0
  46. umap/static/umap/js/modules/importers/cadastrefr.js +2 -2
  47. umap/static/umap/js/modules/importers/communesfr.js +1 -1
  48. umap/static/umap/js/modules/permissions.js +20 -10
  49. umap/static/umap/js/modules/rendering/icon.js +15 -2
  50. umap/static/umap/js/modules/rendering/layers/classified.js +7 -7
  51. umap/static/umap/js/modules/rendering/layers/cluster.js +2 -2
  52. umap/static/umap/js/modules/rendering/layers/heat.js +4 -4
  53. umap/static/umap/js/modules/rendering/map.js +14 -6
  54. umap/static/umap/js/modules/rendering/popup.js +2 -2
  55. umap/static/umap/js/modules/rendering/template.js +3 -3
  56. umap/static/umap/js/modules/rendering/ui.js +17 -11
  57. umap/static/umap/js/modules/rules.js +13 -16
  58. umap/static/umap/js/modules/schema.js +23 -1
  59. umap/static/umap/js/modules/share.js +1 -1
  60. umap/static/umap/js/modules/slideshow.js +1 -0
  61. umap/static/umap/js/modules/sync/engine.js +141 -19
  62. umap/static/umap/js/modules/sync/undo.js +101 -0
  63. umap/static/umap/js/modules/sync/updaters.js +51 -28
  64. umap/static/umap/js/modules/tableeditor.js +1 -1
  65. umap/static/umap/js/modules/ui/bar.js +61 -21
  66. umap/static/umap/js/modules/ui/tooltip.js +1 -1
  67. umap/static/umap/js/modules/umap.js +190 -176
  68. umap/static/umap/js/modules/utils.js +30 -4
  69. umap/static/umap/js/umap.controls.js +82 -38
  70. umap/static/umap/locale/am_ET.js +11 -6
  71. umap/static/umap/locale/am_ET.json +11 -6
  72. umap/static/umap/locale/ar.js +11 -6
  73. umap/static/umap/locale/ar.json +11 -6
  74. umap/static/umap/locale/ast.js +11 -6
  75. umap/static/umap/locale/ast.json +11 -6
  76. umap/static/umap/locale/bg.js +11 -6
  77. umap/static/umap/locale/bg.json +11 -6
  78. umap/static/umap/locale/br.js +12 -7
  79. umap/static/umap/locale/br.json +12 -7
  80. umap/static/umap/locale/ca.js +11 -6
  81. umap/static/umap/locale/ca.json +11 -6
  82. umap/static/umap/locale/cs_CZ.js +11 -6
  83. umap/static/umap/locale/cs_CZ.json +11 -6
  84. umap/static/umap/locale/da.js +11 -6
  85. umap/static/umap/locale/da.json +11 -6
  86. umap/static/umap/locale/de.js +47 -42
  87. umap/static/umap/locale/de.json +47 -42
  88. umap/static/umap/locale/el.js +11 -6
  89. umap/static/umap/locale/el.json +11 -6
  90. umap/static/umap/locale/en.js +11 -6
  91. umap/static/umap/locale/en.json +11 -6
  92. umap/static/umap/locale/en_US.json +11 -6
  93. umap/static/umap/locale/es.js +11 -6
  94. umap/static/umap/locale/es.json +11 -6
  95. umap/static/umap/locale/et.js +11 -6
  96. umap/static/umap/locale/et.json +11 -6
  97. umap/static/umap/locale/eu.js +11 -6
  98. umap/static/umap/locale/eu.json +11 -6
  99. umap/static/umap/locale/fa_IR.js +11 -6
  100. umap/static/umap/locale/fa_IR.json +11 -6
  101. umap/static/umap/locale/fi.js +11 -6
  102. umap/static/umap/locale/fi.json +11 -6
  103. umap/static/umap/locale/fr.js +11 -6
  104. umap/static/umap/locale/fr.json +11 -6
  105. umap/static/umap/locale/gl.js +12 -7
  106. umap/static/umap/locale/gl.json +12 -7
  107. umap/static/umap/locale/he.js +11 -6
  108. umap/static/umap/locale/he.json +11 -6
  109. umap/static/umap/locale/hr.js +11 -6
  110. umap/static/umap/locale/hr.json +11 -6
  111. umap/static/umap/locale/hu.js +25 -20
  112. umap/static/umap/locale/hu.json +25 -20
  113. umap/static/umap/locale/id.js +11 -6
  114. umap/static/umap/locale/id.json +11 -6
  115. umap/static/umap/locale/is.js +151 -146
  116. umap/static/umap/locale/is.json +151 -146
  117. umap/static/umap/locale/it.js +11 -6
  118. umap/static/umap/locale/it.json +11 -6
  119. umap/static/umap/locale/ja.js +11 -6
  120. umap/static/umap/locale/ja.json +11 -6
  121. umap/static/umap/locale/ko.js +11 -6
  122. umap/static/umap/locale/ko.json +11 -6
  123. umap/static/umap/locale/lt.js +11 -6
  124. umap/static/umap/locale/lt.json +11 -6
  125. umap/static/umap/locale/ms.js +11 -6
  126. umap/static/umap/locale/ms.json +11 -6
  127. umap/static/umap/locale/nl.js +12 -7
  128. umap/static/umap/locale/nl.json +12 -7
  129. umap/static/umap/locale/no.js +11 -6
  130. umap/static/umap/locale/no.json +11 -6
  131. umap/static/umap/locale/pl.js +11 -6
  132. umap/static/umap/locale/pl.json +11 -6
  133. umap/static/umap/locale/pl_PL.json +11 -6
  134. umap/static/umap/locale/pt.js +11 -6
  135. umap/static/umap/locale/pt.json +11 -6
  136. umap/static/umap/locale/pt_BR.js +11 -6
  137. umap/static/umap/locale/pt_BR.json +11 -6
  138. umap/static/umap/locale/pt_PT.js +11 -6
  139. umap/static/umap/locale/pt_PT.json +11 -6
  140. umap/static/umap/locale/ro.js +11 -6
  141. umap/static/umap/locale/ro.json +11 -6
  142. umap/static/umap/locale/ru.js +11 -6
  143. umap/static/umap/locale/ru.json +11 -6
  144. umap/static/umap/locale/sk_SK.js +11 -6
  145. umap/static/umap/locale/sk_SK.json +11 -6
  146. umap/static/umap/locale/sl.js +11 -6
  147. umap/static/umap/locale/sl.json +11 -6
  148. umap/static/umap/locale/sr.js +11 -6
  149. umap/static/umap/locale/sr.json +11 -6
  150. umap/static/umap/locale/sv.js +11 -6
  151. umap/static/umap/locale/sv.json +11 -6
  152. umap/static/umap/locale/th_TH.js +11 -6
  153. umap/static/umap/locale/th_TH.json +11 -6
  154. umap/static/umap/locale/tr.js +11 -6
  155. umap/static/umap/locale/tr.json +11 -6
  156. umap/static/umap/locale/uk_UA.js +11 -6
  157. umap/static/umap/locale/uk_UA.json +11 -6
  158. umap/static/umap/locale/vi.js +11 -6
  159. umap/static/umap/locale/vi.json +11 -6
  160. umap/static/umap/locale/vi_VN.json +11 -6
  161. umap/static/umap/locale/zh.js +11 -6
  162. umap/static/umap/locale/zh.json +11 -6
  163. umap/static/umap/locale/zh_CN.json +11 -6
  164. umap/static/umap/locale/zh_TW.Big5.json +11 -6
  165. umap/static/umap/locale/zh_TW.js +19 -14
  166. umap/static/umap/locale/zh_TW.json +19 -14
  167. umap/static/umap/map.css +58 -28
  168. umap/static/umap/unittests/sync.js +0 -57
  169. umap/static/umap/unittests/utils.js +47 -0
  170. umap/static/umap/vars.css +5 -2
  171. umap/static/umap/vendors/photon/leaflet.photon.js +3 -0
  172. umap/sync/payloads.py +3 -2
  173. umap/templates/auth/user_detail.html +1 -1
  174. umap/templates/auth/user_stars.html +1 -1
  175. umap/templates/umap/content.html +17 -12
  176. umap/templates/umap/home.html +7 -5
  177. umap/templates/umap/map_fragment.html +1 -1
  178. umap/templates/umap/map_list.html +20 -13
  179. umap/templates/umap/search.html +7 -3
  180. umap/templates/umap/search_bar.html +13 -11
  181. umap/templates/umap/team_detail.html +1 -1
  182. umap/tests/base.py +2 -1
  183. umap/tests/fixtures/remote_data.umap +55 -0
  184. umap/tests/fixtures/test_upload_data_with_iconurl.umap +122 -0
  185. umap/tests/integration/test_browser.py +1 -3
  186. umap/tests/integration/test_conditional_rules.py +3 -0
  187. umap/tests/integration/test_edit_datalayer.py +2 -7
  188. umap/tests/integration/test_edit_map.py +15 -0
  189. umap/tests/integration/test_edit_polygon.py +1 -2
  190. umap/tests/integration/test_import.py +59 -2
  191. umap/tests/integration/test_optimistic_merge.py +4 -3
  192. umap/tests/integration/test_owned_map.py +0 -1
  193. umap/tests/integration/test_save.py +2 -4
  194. umap/tests/integration/test_undo_redo.py +267 -0
  195. umap/tests/integration/test_websocket_sync.py +78 -11
  196. umap/tests/settings.py +1 -3
  197. umap/tests/test_datalayer_s3.py +1 -0
  198. umap/tests/test_map_views.py +1 -0
  199. umap/tests/test_views.py +34 -0
  200. umap/utils.py +1 -1
  201. umap/views.py +23 -2
  202. {umap_project-2.9.3.dist-info → umap_project-3.0.1.dist-info}/METADATA +13 -12
  203. {umap_project-2.9.3.dist-info → umap_project-3.0.1.dist-info}/RECORD +206 -208
  204. umap/static/umap/js/modules/saving.js +0 -52
  205. umap/static/umap/test/.eslintrc +0 -21
  206. umap/static/umap/test/DataLayer.js +0 -463
  207. umap/static/umap/test/Feature.js +0 -131
  208. umap/static/umap/test/Map.js +0 -37
  209. umap/static/umap/test/Marker.js +0 -126
  210. umap/static/umap/test/Polygon.js +0 -111
  211. umap/static/umap/test/Polyline.js +0 -286
  212. umap/static/umap/test/Util.js +0 -28
  213. umap/static/umap/test/_pre.js +0 -455
  214. umap/static/umap/test/index.html +0 -139
  215. {umap_project-2.9.3.dist-info → umap_project-3.0.1.dist-info}/WHEEL +0 -0
  216. {umap_project-2.9.3.dist-info → umap_project-3.0.1.dist-info}/entry_points.txt +0 -0
  217. {umap_project-2.9.3.dist-info → umap_project-3.0.1.dist-info}/licenses/LICENSE +0 -0
@@ -1,44 +1,41 @@
1
1
  import {
2
2
  DomUtil,
3
3
  Util as LeafletUtil,
4
- stamp,
5
4
  latLngBounds,
5
+ stamp,
6
6
  } from '../../vendors/leaflet/leaflet-src.esm.js'
7
- import { translate, setLocale, getLocale } from './i18n.js'
8
- import * as Utils from './utils.js'
9
- import { ServerStored } from './saving.js'
10
- import * as SAVEMANAGER from './saving.js'
11
- import { SyncEngine } from './sync/engine.js'
12
- import { LeafletMap } from './rendering/map.js'
13
- import URLs from './urls.js'
14
- import { Panel, EditPanel, FullPanel } from './ui/panel.js'
15
- import Dialog from './ui/dialog.js'
16
- import { BottomBar, TopBar, EditBar } from './ui/bar.js'
17
- import Tooltip from './ui/tooltip.js'
18
- import ContextMenu from './ui/contextmenu.js'
19
- import { Request, ServerRequest } from './request.js'
20
- import Help from './help.js'
21
- import { Formatter } from './formatter.js'
22
- import Slideshow from './slideshow.js'
23
- import { MapPermissions } from './permissions.js'
24
- import { SCHEMA } from './schema.js'
25
- import { DataLayer } from './data/layer.js'
26
- import Facets from './facets.js'
7
+ import {
8
+ uMapAlert as Alert,
9
+ uMapAlertCreation as AlertCreation,
10
+ } from '../components/alerts/alert.js'
27
11
  import Browser from './browser.js'
28
12
  import Caption from './caption.js'
13
+ import { DataLayer } from './data/layer.js'
14
+ import Facets from './facets.js'
15
+ import { MutatingForm } from './form/builder.js'
16
+ import { Formatter } from './formatter.js'
17
+ import Help from './help.js'
18
+ import { getLocale, setLocale, translate } from './i18n.js'
29
19
  import Importer from './importer.js'
20
+ import Orderable from './orderable.js'
21
+ import { MapPermissions } from './permissions.js'
22
+ import { LeafletMap } from './rendering/map.js'
23
+ import { Request, ServerRequest } from './request.js'
30
24
  import Rules from './rules.js'
25
+ import { SCHEMA } from './schema.js'
31
26
  import Share from './share.js'
32
- import {
33
- uMapAlertCreation as AlertCreation,
34
- uMapAlert as Alert,
35
- } from '../components/alerts/alert.js'
36
- import Orderable from './orderable.js'
37
- import { MutatingForm } from './form/builder.js'
27
+ import Slideshow from './slideshow.js'
28
+ import { SyncEngine } from './sync/engine.js'
29
+ import { BottomBar, EditBar, TopBar } from './ui/bar.js'
30
+ import ContextMenu from './ui/contextmenu.js'
31
+ import Dialog from './ui/dialog.js'
32
+ import { EditPanel, FullPanel, Panel } from './ui/panel.js'
33
+ import Tooltip from './ui/tooltip.js'
34
+ import URLs from './urls.js'
35
+ import * as Utils from './utils.js'
38
36
 
39
- export default class Umap extends ServerStored {
37
+ export default class Umap {
40
38
  constructor(element, geojson) {
41
- super()
42
39
  // We need to call async function in the init process,
43
40
  // the init itself does not need to be awaited, but some calls
44
41
  // in the process must be blocker
@@ -96,15 +93,19 @@ export default class Umap extends ServerStored {
96
93
  this._leafletMap.latLng(center)
97
94
  }
98
95
 
96
+ // Needed for permissions
97
+ this.syncEngine = new SyncEngine(this)
98
+ this.sync = this.syncEngine.proxy(this)
99
+
99
100
  // Needed to render controls
100
101
  this.permissions = new MapPermissions(this)
101
102
  this.urls = new URLs(this.properties.urls)
102
103
  this.slideshow = new Slideshow(this, this._leafletMap)
103
104
 
104
- this._leafletMap.setup()
105
-
106
105
  if (geojson.properties.schema) this.overrideSchema(geojson.properties.schema)
107
106
 
107
+ this._leafletMap.setup()
108
+
108
109
  this.panel = new Panel(this, this._leafletMap)
109
110
  this.dialog = new Dialog({ className: 'dark' })
110
111
  this.topBar = new TopBar(this, this._leafletMap._controlContainer)
@@ -130,9 +131,6 @@ export default class Umap extends ServerStored {
130
131
  this.share = new Share(this)
131
132
  this.rules = new Rules(this)
132
133
 
133
- this.syncEngine = new SyncEngine(this)
134
- this.sync = this.syncEngine.proxy(this)
135
-
136
134
  if (this.hasEditMode()) {
137
135
  this.editPanel = new EditPanel(this, this._leafletMap)
138
136
  this.fullPanel = new FullPanel(this, this._leafletMap)
@@ -196,7 +194,6 @@ export default class Umap extends ServerStored {
196
194
  // Creation mode
197
195
  if (!this.id) {
198
196
  if (!this.properties.preview) {
199
- this.isDirty = true
200
197
  this.enableEdit()
201
198
  }
202
199
  this._defaultExtent = true
@@ -212,10 +209,14 @@ export default class Umap extends ServerStored {
212
209
  this.propagate()
213
210
  }
214
211
 
215
- window.onbeforeunload = () => (this.editEnabled && SAVEMANAGER.isDirty) || null
212
+ window.onbeforeunload = () => (this.editEnabled && this.isDirty) || null
216
213
  this.backup()
217
214
  }
218
215
 
216
+ get isDirty() {
217
+ return this.sync._undoManager.isDirty()
218
+ }
219
+
219
220
  get editedFeature() {
220
221
  return this._editedFeature
221
222
  }
@@ -349,7 +350,7 @@ export default class Umap extends ServerStored {
349
350
  const items = []
350
351
  if (this.hasEditMode()) {
351
352
  if (this.editEnabled) {
352
- if (!SAVEMANAGER.isDirty) {
353
+ if (!this.isDirty) {
353
354
  items.push({
354
355
  label: this.help.displayLabel('STOP_EDIT'),
355
356
  action: () => this.disableEdit(),
@@ -498,92 +499,88 @@ export default class Umap extends ServerStored {
498
499
  }
499
500
 
500
501
  initShortcuts() {
501
- const globalShortcuts = (event) => {
502
- if (event.key === 'Escape') {
503
- if (this.importer.dialog.visible) {
504
- this.importer.dialog.close()
505
- } else if (this.editEnabled && this._leafletMap.editTools.drawing()) {
506
- this._leafletMap.editTools.onEscape()
507
- } else if (this._leafletMap.measureTools.enabled()) {
508
- this._leafletMap.measureTools.stopDrawing()
509
- } else if (this.fullPanel?.isOpen()) {
510
- this.fullPanel?.close()
511
- } else if (this.editPanel?.isOpen()) {
512
- this.editPanel?.close()
513
- } else if (this.panel.isOpen()) {
514
- this.panel.close()
515
- }
516
- }
517
-
518
- // From now on, only ctrl/meta shortcut
519
- if (!(event.ctrlKey || event.metaKey) || event.shiftKey) return
520
-
521
- if (event.key === 'f') {
522
- event.stopPropagation()
523
- event.preventDefault()
524
- this.search()
525
- }
526
-
527
- /* Edit mode only shortcuts */
528
- if (!this.hasEditMode()) return
529
-
530
- // Edit mode Off
531
- if (!this.editEnabled) {
532
- switch (event.key) {
533
- case 'e':
534
- event.stopPropagation()
535
- event.preventDefault()
536
- this.enableEdit()
537
- break
538
- }
539
- return
540
- }
541
-
542
- // Edit mode on
543
- let used = true
544
- switch (event.key) {
545
- case 'e':
546
- if (!SAVEMANAGER.isDirty) this.disableEdit()
547
- break
548
- case 's':
549
- if (SAVEMANAGER.isDirty) this.saveAll()
550
- break
551
- case 'z':
552
- if (Utils.isWritable(event.target)) {
553
- used = false
554
- break
502
+ const shortcuts = {
503
+ Escape: {
504
+ do: () => {
505
+ if (this.importer.dialog.visible) {
506
+ this.importer.dialog.close()
507
+ } else if (this.editEnabled && this._leafletMap.editTools.drawing()) {
508
+ this._leafletMap.editTools.onEscape()
509
+ } else if (this._leafletMap.measureTools.enabled()) {
510
+ this._leafletMap.measureTools.stopDrawing()
511
+ } else if (this.fullPanel?.isOpen()) {
512
+ this.fullPanel?.close()
513
+ } else if (this.editPanel?.isOpen()) {
514
+ this.editPanel?.close()
515
+ } else if (this.panel.isOpen()) {
516
+ this.panel.close()
555
517
  }
556
- if (SAVEMANAGER.isDirty) {
557
- this.askForReset()
558
- }
559
- break
560
- case 'm':
561
- this._leafletMap.editTools.startMarker()
562
- break
563
- case 'p':
564
- this._leafletMap.editTools.startPolygon()
565
- break
566
- case 'l':
567
- this._leafletMap.editTools.startPolyline()
568
- break
569
- case 'i':
570
- this.importer.open()
571
- break
572
- case 'o':
573
- this.importer.openFiles()
574
- break
575
- case 'h':
576
- this.help.showGetStarted()
577
- break
578
- default:
579
- used = false
580
- }
581
- if (used) {
518
+ },
519
+ },
520
+ 'Ctrl+f': {
521
+ do: () => {
522
+ this.search()
523
+ },
524
+ },
525
+ 'Ctrl+e': {
526
+ if: () => this.hasEditMode(),
527
+ do: () => {
528
+ console.log('doing')
529
+ if (!this.editEnabled) this.enableEdit()
530
+ else if (!this.isDirty) this.disableEdit()
531
+ },
532
+ },
533
+ 'Ctrl+s': {
534
+ if: () => this.editEnabled && this.isDirty,
535
+ do: () => this.saveAll(),
536
+ },
537
+ 'Ctrl+z': {
538
+ if: () => this.editEnabled && !Utils.isWritable(event.target),
539
+ do: () => this.sync._undoManager.undo(),
540
+ },
541
+ 'Ctrl+Shift+Z': {
542
+ if: () => this.editEnabled && !Utils.isWritable(event.target),
543
+ do: () => this.sync._undoManager.redo(),
544
+ },
545
+ 'Ctrl+m': {
546
+ if: () => this.editEnabled,
547
+ do: () => this._leafletMap.editTools.startMarker(),
548
+ },
549
+ 'Ctrl+p': {
550
+ if: () => this.editEnabled,
551
+ do: () => this._leafletMap.editTools.startPolygon(),
552
+ },
553
+ 'Ctrl+l': {
554
+ if: () => this.editEnabled,
555
+ do: () => this._leafletMap.editTools.startPolyline(),
556
+ },
557
+ 'Ctrl+i': {
558
+ if: () => this.editEnabled,
559
+ do: () => this.importer.open(),
560
+ },
561
+ 'Ctrl+o': {
562
+ if: () => this.editEnabled,
563
+ do: () => this.importer.openFiles(),
564
+ },
565
+ 'Ctrl+h': {
566
+ if: () => this.editEnabled,
567
+ do: () => this.help.showGetStarted(),
568
+ },
569
+ }
570
+ const onKeyDown = (event) => {
571
+ const shiftKey = event.shiftKey ? 'Shift+' : ''
572
+ const altKey = event.altKey ? 'Alt+' : ''
573
+ const ctrlKey = event.ctrlKey || event.metaKey ? 'Ctrl+' : ''
574
+ const combination = `${ctrlKey}${shiftKey}${altKey}${event.key}`
575
+
576
+ const shortcut = shortcuts[combination]
577
+ if (shortcut && (!shortcut.if || shortcut.if())) {
578
+ shortcut.do()
582
579
  event.stopPropagation()
583
580
  event.preventDefault()
584
581
  }
585
582
  }
586
- document.addEventListener('keydown', globalShortcuts)
583
+ document.addEventListener('keydown', onKeyDown)
587
584
  }
588
585
 
589
586
  async initDataLayers(datalayers) {
@@ -671,10 +668,10 @@ export default class Umap extends ServerStored {
671
668
  }
672
669
 
673
670
  async saveAll() {
674
- if (!SAVEMANAGER.isDirty) return
671
+ if (!this.isDirty) return
675
672
  if (this._defaultExtent) this._setCenterAndZoom()
676
673
  this.backup()
677
- await SAVEMANAGER.save()
674
+ await this.sync.save()
678
675
  // Do a blind render for now, as we are not sure what could
679
676
  // have changed, we'll be more subtil when we'll remove the
680
677
  // save action
@@ -685,7 +682,6 @@ export default class Umap extends ServerStored {
685
682
  Alert.success(translate('Map has been saved!'))
686
683
  })
687
684
  }
688
- this.sync.saved()
689
685
  this.fire('saved')
690
686
  }
691
687
 
@@ -757,6 +753,12 @@ export default class Umap extends ServerStored {
757
753
  const form = builder.build()
758
754
  container.appendChild(form)
759
755
 
756
+ const tags = DomUtil.createFieldset(container, translate('Tags'))
757
+ const tagsFields = ['properties.tags']
758
+ const tagsBuilder = new MutatingForm(this, tagsFields, {
759
+ umap: this,
760
+ })
761
+ tags.appendChild(tagsBuilder.build())
760
762
  const credits = DomUtil.createFieldset(container, translate('Credits'))
761
763
  const creditsFields = [
762
764
  'properties.licence',
@@ -1019,35 +1021,36 @@ export default class Umap extends ServerStored {
1019
1021
  'button',
1020
1022
  boundsButtons,
1021
1023
  translate('Use current bounds'),
1022
- function () {
1024
+ () => {
1023
1025
  const bounds = this._leafletMap.getBounds()
1026
+ const oldLimitBounds = { ...this.properties.limitBounds }
1024
1027
  this.properties.limitBounds.south = LeafletUtil.formatNum(bounds.getSouth())
1025
1028
  this.properties.limitBounds.west = LeafletUtil.formatNum(bounds.getWest())
1026
1029
  this.properties.limitBounds.north = LeafletUtil.formatNum(bounds.getNorth())
1027
1030
  this.properties.limitBounds.east = LeafletUtil.formatNum(bounds.getEast())
1028
1031
  boundsBuilder.fetchAll()
1029
-
1030
- this.sync.update(this, 'properties.limitBounds', this.properties.limitBounds)
1031
- this.isDirty = true
1032
- this._leafletMap.handleLimitBounds()
1033
- },
1034
- this
1035
- )
1036
- DomUtil.createButton(
1037
- 'button',
1038
- boundsButtons,
1039
- translate('Empty'),
1040
- function () {
1041
- this.properties.limitBounds.south = null
1042
- this.properties.limitBounds.west = null
1043
- this.properties.limitBounds.north = null
1044
- this.properties.limitBounds.east = null
1045
- boundsBuilder.fetchAll()
1046
- this.isDirty = true
1032
+ this.sync.update(
1033
+ 'properties.limitBounds',
1034
+ this.properties.limitBounds,
1035
+ oldLimitBounds
1036
+ )
1047
1037
  this._leafletMap.handleLimitBounds()
1048
- },
1049
- this
1038
+ }
1050
1039
  )
1040
+ DomUtil.createButton('button', boundsButtons, translate('Empty'), () => {
1041
+ const oldLimitBounds = { ...this.properties.limitBounds }
1042
+ this.properties.limitBounds.south = null
1043
+ this.properties.limitBounds.west = null
1044
+ this.properties.limitBounds.north = null
1045
+ this.properties.limitBounds.east = null
1046
+ boundsBuilder.fetchAll()
1047
+ this._leafletMap.handleLimitBounds()
1048
+ this.sync.update(
1049
+ 'properties.limitBounds',
1050
+ this.properties.limitBounds,
1051
+ oldLimitBounds
1052
+ )
1053
+ })
1051
1054
  }
1052
1055
 
1053
1056
  _editSlideshow(container) {
@@ -1160,23 +1163,7 @@ export default class Umap extends ServerStored {
1160
1163
  })
1161
1164
  }
1162
1165
 
1163
- reset() {
1164
- if (this._leafletMap.editTools) this._leafletMap.editTools.stopDrawing()
1165
- this.resetProperties()
1166
- this.datalayersIndex = [].concat(this._datalayersIndex_bk)
1167
- // Iter over all datalayers, including deleted if any.
1168
- for (const datalayer of Object.values(this.datalayers)) {
1169
- if (datalayer.isDeleted) datalayer.connectToMap()
1170
- if (datalayer.isDirty) datalayer.reset()
1171
- }
1172
- this.ensurePanesOrder()
1173
- this._leafletMap.initTileLayers()
1174
- this.onDataLayersChanged()
1175
- this.isDirty = !this.id
1176
- }
1177
-
1178
1166
  async save() {
1179
- this.rules.commit()
1180
1167
  const geojson = {
1181
1168
  type: 'Feature',
1182
1169
  geometry: this.geometry(),
@@ -1185,6 +1172,7 @@ export default class Umap extends ServerStored {
1185
1172
  const formData = new FormData()
1186
1173
  formData.append('name', this.properties.name)
1187
1174
  formData.append('center', JSON.stringify(this.geometry()))
1175
+ formData.append('tags', this.properties.tags || [])
1188
1176
  formData.append('settings', JSON.stringify(geojson))
1189
1177
  const uri = this.urls.get('map_save', { map_id: this.id })
1190
1178
  const [data, _, error] = await this.server.post(uri, {}, formData)
@@ -1301,16 +1289,6 @@ export default class Umap extends ServerStored {
1301
1289
  this._leafletMap.fire(name)
1302
1290
  }
1303
1291
 
1304
- askForReset(e) {
1305
- if (this.getProperty('syncEnabled')) return
1306
- this.dialog
1307
- .confirm(translate('Are you sure you want to cancel your changes?'))
1308
- .then(() => {
1309
- this.reset()
1310
- this.disableEdit()
1311
- })
1312
- }
1313
-
1314
1292
  async initSyncEngine() {
1315
1293
  // this.properties.websocketEnabled is set by the server admin
1316
1294
  if (this.properties.websocketEnabled === false) return
@@ -1324,11 +1302,14 @@ export default class Umap extends ServerStored {
1324
1302
 
1325
1303
  getSyncMetadata() {
1326
1304
  return {
1327
- engine: this.sync,
1328
1305
  subject: 'map',
1329
1306
  }
1330
1307
  }
1331
1308
 
1309
+ onPropertiesUpdated(fields = []) {
1310
+ this._leafletMap.pullProperties()
1311
+ }
1312
+
1332
1313
  render(fields = []) {
1333
1314
  // Propagate will remove the fields it has already
1334
1315
  // processed
@@ -1344,6 +1325,9 @@ export default class Umap extends ServerStored {
1344
1325
  this.bottomBar.redraw()
1345
1326
  break
1346
1327
  case 'data':
1328
+ if (fields.includes('properties.rules')) {
1329
+ this.rules.load()
1330
+ }
1347
1331
  this.eachVisibleDataLayer((datalayer) => {
1348
1332
  datalayer.redraw()
1349
1333
  })
@@ -1518,7 +1502,7 @@ export default class Umap extends ServerStored {
1518
1502
  const form = builder.build()
1519
1503
  row.appendChild(form)
1520
1504
  row.classList.toggle('off', !datalayer.isVisible())
1521
- row.dataset.id = stamp(datalayer)
1505
+ row.dataset.id = datalayer.id
1522
1506
  })
1523
1507
  const onReorder = (src, dst, initialIndex, finalIndex) => {
1524
1508
  const movedLayer = this.datalayers[src.dataset.id]
@@ -1549,7 +1533,7 @@ export default class Umap extends ServerStored {
1549
1533
  }
1550
1534
 
1551
1535
  getDataLayerByUmapId(id) {
1552
- const datalayer = this.findDataLayer((d) => d.id === id)
1536
+ const datalayer = this.datalayers[id]
1553
1537
  if (!datalayer) throw new Error(`Can't find datalayer with id ${id}`)
1554
1538
  return datalayer
1555
1539
  }
@@ -1630,7 +1614,16 @@ export default class Umap extends ServerStored {
1630
1614
 
1631
1615
  importRaw(rawData) {
1632
1616
  const importedData = JSON.parse(rawData)
1633
-
1617
+ let remoteOrigin = ''
1618
+ if (importedData.uri) {
1619
+ const uri = new URL(importedData.uri)
1620
+ if (uri.origin !== window.location.origin) {
1621
+ remoteOrigin = uri.origin
1622
+ }
1623
+ }
1624
+ if (importedData.properties?.iconUrl?.startsWith('/')) {
1625
+ importedData.properties.iconUrl = remoteOrigin + importedData.properties.iconUrl
1626
+ }
1634
1627
  this.setProperties(importedData.properties)
1635
1628
 
1636
1629
  if (importedData.geometry) {
@@ -1642,6 +1635,9 @@ export default class Umap extends ServerStored {
1642
1635
  delete geojson._storage
1643
1636
  }
1644
1637
  delete geojson._umap_options?.id // Never trust an id at this stage
1638
+ if (geojson._umap_options?.iconUrl?.startsWith('/')) {
1639
+ geojson._umap_options.iconUrl = remoteOrigin + geojson._umap_options.iconUrl
1640
+ }
1645
1641
  const dataLayer = this.createDirtyDataLayer(geojson._umap_options)
1646
1642
  dataLayer.fromUmapGeoJSON(geojson)
1647
1643
  }
@@ -1653,7 +1649,6 @@ export default class Umap extends ServerStored {
1653
1649
  )
1654
1650
  this.render(fields)
1655
1651
  this._leafletMap._setDefaultCenter()
1656
- this.isDirty = true
1657
1652
  }
1658
1653
 
1659
1654
  importUmapFile(file) {
@@ -1747,14 +1742,33 @@ export default class Umap extends ServerStored {
1747
1742
  }
1748
1743
 
1749
1744
  setCenterAndZoom() {
1750
- this._setCenterAndZoom()
1745
+ this._setCenterAndZoom(true)
1751
1746
  Alert.success(translate('The zoom and center have been modified.'))
1752
1747
  }
1753
1748
 
1754
- _setCenterAndZoom() {
1749
+ _setCenterAndZoom(manual) {
1750
+ const oldCenter = { ...this.properties.center }
1751
+ const oldZoom = this.properties.zoom
1755
1752
  this.properties.center = this._leafletMap.getCenter()
1756
1753
  this.properties.zoom = this._leafletMap.getZoom()
1757
- this.isDirty = true
1758
1754
  this._defaultExtent = false
1755
+ if (manual) {
1756
+ this.sync.startBatch()
1757
+ this.sync.update('properties.center', this.properties.center, oldCenter)
1758
+ this.sync.update('properties.zoom', this.properties.zoom, oldZoom)
1759
+ this.sync.commitBatch()
1760
+ }
1761
+ }
1762
+
1763
+ getStaticPathFor(name) {
1764
+ return SCHEMA.iconUrl.default.replace('marker.svg', name)
1765
+ }
1766
+
1767
+ undo() {
1768
+ this.sync._undoManager.undo()
1769
+ }
1770
+
1771
+ redo() {
1772
+ this.sync._undoManager.redo()
1759
1773
  }
1760
1774
  }
@@ -117,6 +117,7 @@ export function escapeHTML(s) {
117
117
  'dd',
118
118
  'b',
119
119
  'i',
120
+ 'kbd',
120
121
  ],
121
122
  ADD_ATTR: [
122
123
  'target',
@@ -125,6 +126,7 @@ export function escapeHTML(s) {
125
126
  'frameborder',
126
127
  'scrolling',
127
128
  'controls',
129
+ 'class',
128
130
  ],
129
131
  ALLOWED_ATTR: ['href', 'src', 'width', 'height', 'style', 'dir', 'title', 'type'],
130
132
  // Added: `geo:` URL scheme as defined in RFC5870:
@@ -368,10 +370,13 @@ export function isDataImage(value) {
368
370
  * characters and no diacritics.
369
371
  */
370
372
  export function normalize(s) {
371
- return (s || '')
372
- .toLowerCase()
373
- .normalize('NFD')
374
- .replace(/[\u0300-\u036f]/g, '')
373
+ return (
374
+ (s || '')
375
+ .toLowerCase()
376
+ .normalize('NFD')
377
+ // biome-ignore lint/suspicious/noMisleadingCharacterClass: <explanation>
378
+ .replace(/[\u0300-\u036f]/g, '')
379
+ )
375
380
  }
376
381
 
377
382
  // Vendorized from leaflet.utils
@@ -481,6 +486,27 @@ export const debounce = (callback, wait) => {
481
486
  }
482
487
  }
483
488
 
489
+ export function setObjectValue(obj, key, value) {
490
+ const parts = key.split('.')
491
+ const lastKey = parts.pop()
492
+
493
+ // Reduce the current list of attributes,
494
+ // to find the object to set the property onto
495
+ const objectToSet = parts.reduce((currentObj, part) => {
496
+ if (currentObj !== undefined && part in currentObj) return currentObj[part]
497
+ }, obj)
498
+
499
+ // In case the given path doesn't exist, stop here
500
+ if (objectToSet === undefined) return
501
+
502
+ // Set the value (or delete it)
503
+ if (typeof value === 'undefined') {
504
+ delete objectToSet[lastKey]
505
+ } else {
506
+ objectToSet[lastKey] = value
507
+ }
508
+ }
509
+
484
510
  export const COLORS = [
485
511
  'Black',
486
512
  'Navy',