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
@@ -44,6 +44,10 @@ export const SCHEMA = {
44
44
  type: Object,
45
45
  impacts: ['data'],
46
46
  },
47
+ center: {
48
+ type: Object,
49
+ impacts: [], // default center, doesn't need any update of the map
50
+ },
47
51
  color: {
48
52
  type: String,
49
53
  impacts: ['data'],
@@ -118,6 +122,9 @@ export const SCHEMA = {
118
122
  default: false,
119
123
  label: translate('Animated transitions'),
120
124
  },
125
+ edit_status: {
126
+ type: Number,
127
+ },
121
128
  editinosmControl: {
122
129
  type: Boolean,
123
130
  impacts: ['ui'],
@@ -125,6 +132,9 @@ export const SCHEMA = {
125
132
  label: translate('Display the control to open OpenStreetMap editor'),
126
133
  default: null,
127
134
  },
135
+ editors: {
136
+ type: Array,
137
+ },
128
138
  embedControl: {
129
139
  type: Boolean,
130
140
  impacts: ['ui'],
@@ -204,6 +214,7 @@ export const SCHEMA = {
204
214
  ['Circle', translate('Circle')],
205
215
  ['Drop', translate('Drop')],
206
216
  ['Ball', translate('Ball')],
217
+ ['Raw', translate('None')],
207
218
  ],
208
219
  default: 'Default',
209
220
  },
@@ -362,6 +373,9 @@ export const SCHEMA = {
362
373
  type: Object,
363
374
  impacts: ['background'],
364
375
  },
376
+ owner: {
377
+ type: Object,
378
+ },
365
379
  permanentCredit: {
366
380
  type: 'Text',
367
381
  impacts: ['ui'],
@@ -436,6 +450,9 @@ export const SCHEMA = {
436
450
  label: translate('Display the search control'),
437
451
  default: true,
438
452
  },
453
+ share_status: {
454
+ type: Number,
455
+ },
439
456
  shortCredit: {
440
457
  type: String,
441
458
  impacts: ['ui'],
@@ -500,6 +517,12 @@ export const SCHEMA = {
500
517
  helpEntries: ['sync'],
501
518
  default: false,
502
519
  },
520
+ tags: {
521
+ type: Array,
522
+ },
523
+ team: {
524
+ type: Object,
525
+ },
503
526
  tilelayer: {
504
527
  type: Object,
505
528
  impacts: ['background'],
@@ -566,7 +589,6 @@ export const SCHEMA = {
566
589
  type: Object,
567
590
  impacts: ['data'],
568
591
  },
569
-
570
592
  _referenceVersion: {
571
593
  type: Number,
572
594
  impacts: ['data'],
@@ -1,8 +1,8 @@
1
1
  import { DomUtil } from '../../vendors/leaflet/leaflet-src.esm.js'
2
+ import { MutatingForm } from './form/builder.js'
2
3
  import { EXPORT_FORMATS } from './formatter.js'
3
4
  import { translate } from './i18n.js'
4
5
  import * as Utils from './utils.js'
5
- import { MutatingForm } from './form/builder.js'
6
6
 
7
7
  export default class Share {
8
8
  constructor(umap) {
@@ -18,6 +18,7 @@ export default class Slideshow extends WithTemplate {
18
18
  this._umap = umap
19
19
  this._id = null
20
20
  this.CLASSNAME = 'umap-slideshow-active'
21
+ this._umap.properties.slideshow ??= {}
21
22
  this.load()
22
23
  this._current = null
23
24
 
@@ -1,8 +1,14 @@
1
1
  import * as Utils from '../utils.js'
2
2
  import { HybridLogicalClock } from './hlc.js'
3
- import { DataLayerUpdater, FeatureUpdater, MapUpdater } from './updaters.js'
3
+ import { UndoManager } from './undo.js'
4
+ import {
5
+ DataLayerUpdater,
6
+ FeatureUpdater,
7
+ MapUpdater,
8
+ MapPermissionsUpdater,
9
+ DataLayerPermissionsUpdater,
10
+ } from './updaters.js'
4
11
  import { WebSocketTransport } from './websocket.js'
5
- import * as SaveManager from '../saving.js'
6
12
 
7
13
  // Start reconnecting after 2 seconds, then double the delay each time
8
14
  // maxing out at 32 seconds.
@@ -55,6 +61,8 @@ export class SyncEngine {
55
61
  map: new MapUpdater(umap),
56
62
  feature: new FeatureUpdater(umap),
57
63
  datalayer: new DataLayerUpdater(umap),
64
+ mappermissions: new MapPermissionsUpdater(umap),
65
+ datalayerpermissions: new DataLayerPermissionsUpdater(umap),
58
66
  }
59
67
  this.transport = undefined
60
68
  this._operations = new Operations()
@@ -64,6 +72,7 @@ export class SyncEngine {
64
72
  this.websocketConnected = false
65
73
  this.closeRequested = false
66
74
  this.peerId = Utils.generateId()
75
+ this._undoManager = new UndoManager(umap, this.updaters, this)
67
76
  }
68
77
 
69
78
  get isOpen() {
@@ -122,16 +131,107 @@ export class SyncEngine {
122
131
  await this.authenticate()
123
132
  }, this._reconnectDelay)
124
133
  }
125
- upsert(subject, metadata, value) {
126
- this._send({ verb: 'upsert', subject, metadata, value })
134
+
135
+ startBatch() {
136
+ this._batch = []
137
+ }
138
+
139
+ commitBatch(subject, metadata) {
140
+ if (!this._batch.length) {
141
+ this._batch = null
142
+ return
143
+ }
144
+ const operations = this._batch.map((stage) => stage.operation)
145
+ const operation = { verb: 'batch', operations, subject, metadata }
146
+ this._undoManager.add({ operation, stages: this._batch })
147
+ this._send(operation)
148
+ this._batch = null
149
+ }
150
+
151
+ upsert(subject, metadata, value, oldValue) {
152
+ const operation = {
153
+ verb: 'upsert',
154
+ subject,
155
+ metadata,
156
+ value,
157
+ }
158
+ const stage = {
159
+ operation,
160
+ newValue: value,
161
+ oldValue: oldValue,
162
+ }
163
+ if (this._batch) {
164
+ this._batch.push(stage)
165
+ return
166
+ }
167
+ this._undoManager.add(stage)
168
+ this._send(operation)
127
169
  }
128
170
 
129
- update(subject, metadata, key, value) {
130
- this._send({ verb: 'update', subject, metadata, key, value })
171
+ update(subject, metadata, key, value, oldValue, { undo } = { undo: true }) {
172
+ const operation = {
173
+ verb: 'update',
174
+ subject,
175
+ metadata,
176
+ key,
177
+ value,
178
+ }
179
+ const stage = {
180
+ operation,
181
+ oldValue: oldValue,
182
+ newValue: value,
183
+ }
184
+ if (this._batch) {
185
+ this._batch.push(stage)
186
+ return
187
+ }
188
+ if (undo) this._undoManager.add(stage)
189
+ this._send(operation)
190
+ }
191
+
192
+ delete(subject, metadata, oldValue) {
193
+ const operation = {
194
+ verb: 'delete',
195
+ subject,
196
+ metadata,
197
+ }
198
+ const stage = {
199
+ operation,
200
+ oldValue: oldValue,
201
+ }
202
+ if (this._batch) {
203
+ this._batch.push(stage)
204
+ return
205
+ }
206
+ this._undoManager.add(stage)
207
+ this._send(operation)
131
208
  }
132
209
 
133
- delete(subject, metadata, key) {
134
- this._send({ verb: 'delete', subject, metadata, key })
210
+ async save() {
211
+ const needSave = new Map()
212
+ if (!this._umap.id) {
213
+ // There is no operation for first map save
214
+ needSave.set(this._umap, [])
215
+ }
216
+ for (const operation of this._operations.sorted()) {
217
+ if (operation.dirty) {
218
+ const updater = this._getUpdater(operation.subject)
219
+ const obj = updater.getStoredObject(operation.metadata)
220
+ if (!needSave.has(obj)) {
221
+ needSave.set(obj, [])
222
+ }
223
+ needSave.get(obj).push(operation)
224
+ }
225
+ }
226
+ for (const [obj, operations] of needSave.entries()) {
227
+ const ok = await obj.save()
228
+ if (!ok) break
229
+ for (const operation of operations) {
230
+ operation.dirty = false
231
+ }
232
+ }
233
+ this.saved()
234
+ this._undoManager.toggleState()
135
235
  }
136
236
 
137
237
  saved() {
@@ -144,8 +244,8 @@ export class SyncEngine {
144
244
  }
145
245
  }
146
246
 
147
- _send(inputMessage) {
148
- const message = this._operations.addLocal(inputMessage)
247
+ _send(operation) {
248
+ const message = this._operations.addLocal(operation)
149
249
 
150
250
  if (this.offline) return
151
251
  if (this.transport) {
@@ -153,7 +253,11 @@ export class SyncEngine {
153
253
  }
154
254
  }
155
255
 
156
- _getUpdater(subject, metadata) {
256
+ _getUpdater(subject, metadata, sync) {
257
+ // For now, prevent permissions to be synced, for security reasons
258
+ if (sync && (subject === 'mappermissions' || subject === 'datalayerpermissions')) {
259
+ return
260
+ }
157
261
  if (Object.keys(this.updaters).includes(subject)) {
158
262
  return this.updaters[subject]
159
263
  }
@@ -161,7 +265,15 @@ export class SyncEngine {
161
265
  }
162
266
 
163
267
  _applyOperation(operation) {
268
+ if (operation.verb === 'batch') {
269
+ operation.operations.map((op) => this._applyOperation(op))
270
+ return
271
+ }
164
272
  const updater = this._getUpdater(operation.subject, operation.metadata)
273
+ if (!updater) {
274
+ debug('No updater for', operation)
275
+ return
276
+ }
165
277
  updater.applyMessage(operation)
166
278
  }
167
279
 
@@ -304,9 +416,8 @@ export class SyncEngine {
304
416
 
305
417
  onSavedMessage({ sender, lastKnownHLC }) {
306
418
  debug(`received saved message from peer ${sender}`, lastKnownHLC)
307
- if (lastKnownHLC === this._operations.getLastKnownHLC() && SaveManager.isDirty) {
308
- SaveManager.clear()
309
- }
419
+ this._operations.saved(lastKnownHLC)
420
+ this._undoManager.toggleState()
310
421
  }
311
422
 
312
423
  /**
@@ -356,7 +467,7 @@ export class SyncEngine {
356
467
  const handler = {
357
468
  get(target, prop) {
358
469
  // Only proxy these methods
359
- if (['upsert', 'update', 'delete'].includes(prop)) {
470
+ if (['upsert', 'update', 'delete', 'commitBatch'].includes(prop)) {
360
471
  const { subject, metadata } = object.getSyncMetadata()
361
472
  // Reflect.get is calling the original method.
362
473
  // .bind is adding the parameters automatically
@@ -378,16 +489,22 @@ export class Operations {
378
489
  this._operations = new Array()
379
490
  }
380
491
 
492
+ saved(hlc) {
493
+ for (const operation of this.getOperationsBefore(hlc)) {
494
+ operation.dirty = false
495
+ }
496
+ }
497
+
381
498
  /**
382
499
  * Tick the clock and store the passed message in the operations list.
383
500
  *
384
501
  * @param {*} inputMessage
385
502
  * @returns {*} clock-aware message
386
503
  */
387
- addLocal(inputMessage) {
388
- const message = { ...inputMessage, hlc: this._hlc.tick() }
389
- this._operations.push(message)
390
- return message
504
+ addLocal(operation) {
505
+ operation.hlc = this._hlc.tick()
506
+ this._operations.push(operation)
507
+ return operation
391
508
  }
392
509
 
393
510
  /**
@@ -445,6 +562,11 @@ export class Operations {
445
562
  return this._operations.filter((op) => op.hlc > hlc)
446
563
  }
447
564
 
565
+ getOperationsBefore(hlc) {
566
+ if (!hlc) return this._operations
567
+ return this._operations.filter((op) => op.hlc <= hlc)
568
+ }
569
+
448
570
  /**
449
571
  * Returns the last known HLC value.
450
572
  */
@@ -0,0 +1,101 @@
1
+ import * as Utils from '../utils.js'
2
+ import { DataLayerUpdater, FeatureUpdater, MapUpdater } from './updaters.js'
3
+
4
+ export class UndoManager {
5
+ constructor(umap, updaters, syncEngine) {
6
+ this._umap = umap
7
+ this._syncEngine = syncEngine
8
+ this.updaters = updaters
9
+ this._undoStack = []
10
+ this._redoStack = []
11
+ }
12
+
13
+ toggleState() {
14
+ // document is undefined during unittests
15
+ if (typeof document === 'undefined') return
16
+ const undoButton = document.querySelector('.edit-undo')
17
+ const redoButton = document.querySelector('.edit-redo')
18
+ if (undoButton) undoButton.disabled = !this._undoStack.length
19
+ if (redoButton) redoButton.disabled = !this._redoStack.length
20
+ const dirty = this.isDirty()
21
+ document.body.classList.toggle('umap-is-dirty', dirty)
22
+ for (const button of document.querySelectorAll('.disabled-on-dirty')) {
23
+ button.disabled = dirty
24
+ }
25
+ for (const button of document.querySelectorAll('.enabled-on-dirty')) {
26
+ button.disabled = !dirty
27
+ }
28
+ }
29
+
30
+ isDirty() {
31
+ if (!this._umap.id) return true
32
+ for (const stage of this._undoStack) {
33
+ if (stage.operation.dirty) return true
34
+ }
35
+ for (const stage of this._redoStack) {
36
+ if (stage.operation.dirty) return true
37
+ }
38
+ return false
39
+ }
40
+
41
+ add(stage) {
42
+ stage.operation.dirty = true
43
+ this._redoStack = []
44
+ this._undoStack.push(stage)
45
+ this.toggleState()
46
+ }
47
+
48
+ copyOperation(stage, redo) {
49
+ const operation = Utils.CopyJSON(stage.operation)
50
+ const value = redo ? stage.newValue : stage.oldValue
51
+ operation.value = value
52
+ if (['delete', 'upsert'].includes(operation.verb)) {
53
+ operation.verb = value === null || value === undefined ? 'delete' : 'upsert'
54
+ }
55
+ return operation
56
+ }
57
+
58
+ undo(redo = false) {
59
+ const fromStack = redo ? this._redoStack : this._undoStack
60
+ const toStack = redo ? this._undoStack : this._redoStack
61
+ const stage = fromStack.pop()
62
+ if (!stage) return
63
+ stage.operation.dirty = !stage.operation.dirty
64
+ if (stage.operation.verb === 'batch') {
65
+ for (const st of stage.stages) {
66
+ this.applyOperation(this.copyOperation(st, redo))
67
+ }
68
+ } else {
69
+ this.applyOperation(this.copyOperation(stage, redo))
70
+ }
71
+ toStack.push(stage)
72
+ this.toggleState()
73
+ }
74
+
75
+ redo() {
76
+ this.undo(true)
77
+ }
78
+
79
+ applyOperation(operation) {
80
+ const updater = this._getUpdater(operation.subject, operation.metadata)
81
+ switch (operation.verb) {
82
+ case 'update':
83
+ updater.update(operation)
84
+ break
85
+ case 'delete':
86
+ updater.delete(operation)
87
+ break
88
+ case 'upsert':
89
+ updater.upsert(operation)
90
+ break
91
+ }
92
+ this._syncEngine._send(operation)
93
+ }
94
+
95
+ _getUpdater(subject, metadata) {
96
+ if (Object.keys(this.updaters).includes(subject)) {
97
+ return this.updaters[subject]
98
+ }
99
+ throw new Error(`Unknown updater ${subject}, ${metadata}`)
100
+ }
101
+ }
@@ -1,4 +1,4 @@
1
- import { fieldInSchema } from '../utils.js'
1
+ import * as Utils from '../utils.js'
2
2
 
3
3
  /**
4
4
  * Updaters are classes able to convert messages
@@ -10,27 +10,6 @@ class BaseUpdater {
10
10
  this._umap = umap
11
11
  }
12
12
 
13
- updateObjectValue(obj, key, value) {
14
- const parts = key.split('.')
15
- const lastKey = parts.pop()
16
-
17
- // Reduce the current list of attributes,
18
- // to find the object to set the property onto
19
- const objectToSet = parts.reduce((currentObj, part) => {
20
- if (currentObj !== undefined && part in currentObj) return currentObj[part]
21
- }, obj)
22
-
23
- // In case the given path doesn't exist, stop here
24
- if (objectToSet === undefined) return
25
-
26
- // Set the value (or delete it)
27
- if (typeof value === 'undefined') {
28
- delete objectToSet[lastKey]
29
- } else {
30
- objectToSet[lastKey] = value
31
- }
32
- }
33
-
34
13
  getDataLayerFromID(layerId) {
35
14
  return this._umap.getDataLayerByUmapId(layerId)
36
15
  }
@@ -43,12 +22,17 @@ class BaseUpdater {
43
22
 
44
23
  export class MapUpdater extends BaseUpdater {
45
24
  update({ key, value }) {
46
- if (fieldInSchema(key)) {
47
- this.updateObjectValue(this._umap, key, value)
25
+ if (Utils.fieldInSchema(key)) {
26
+ Utils.setObjectValue(this._umap, key, value)
48
27
  }
49
28
 
29
+ this._umap.onPropertiesUpdated([key])
50
30
  this._umap.render([key])
51
31
  }
32
+
33
+ getStoredObject() {
34
+ return this._umap
35
+ }
52
36
  }
53
37
 
54
38
  export class DataLayerUpdater extends BaseUpdater {
@@ -57,14 +41,21 @@ export class DataLayerUpdater extends BaseUpdater {
57
41
  try {
58
42
  this.getDataLayerFromID(value.id)
59
43
  } catch {
60
- this._umap.createDataLayer(value, false)
44
+ const datalayer = this._umap.createDataLayer(value._umap_options || value, false)
45
+ if (value.features) {
46
+ // FIXME: this will create new stages in the undoStack, thus this will empty
47
+ // the redoStack
48
+ datalayer.addData(value)
49
+ }
61
50
  }
62
51
  }
63
52
 
64
53
  update({ key, metadata, value }) {
65
54
  const datalayer = this.getDataLayerFromID(metadata.id)
66
- if (fieldInSchema(key)) {
67
- this.updateObjectValue(datalayer, key, value)
55
+ if (key === 'options') {
56
+ datalayer.setOptions(value)
57
+ } else if (Utils.fieldInSchema(key)) {
58
+ Utils.setObjectValue(datalayer, key, value)
68
59
  } else {
69
60
  console.debug(
70
61
  'Not applying update for datalayer because key is not in the schema',
@@ -81,6 +72,10 @@ export class DataLayerUpdater extends BaseUpdater {
81
72
  datalayer.commitDelete()
82
73
  }
83
74
  }
75
+
76
+ getStoredObject(metadata) {
77
+ return this.getDataLayerFromID(metadata.id)
78
+ }
84
79
  }
85
80
 
86
81
  export class FeatureUpdater extends BaseUpdater {
@@ -113,7 +108,7 @@ export class FeatureUpdater extends BaseUpdater {
113
108
  const feature = this.getFeatureFromMetadata(metadata)
114
109
  feature.geometry = value
115
110
  } else {
116
- this.updateObjectValue(feature, key, value)
111
+ Utils.setObjectValue(feature, key, value)
117
112
  feature.datalayer.indexProperties(feature)
118
113
  }
119
114
 
@@ -126,4 +121,32 @@ export class FeatureUpdater extends BaseUpdater {
126
121
  const feature = this.getFeatureFromMetadata(metadata)
127
122
  if (feature) feature.del(false)
128
123
  }
124
+
125
+ getStoredObject(metadata) {
126
+ return this.getDataLayerFromID(metadata.layerId)
127
+ }
128
+ }
129
+
130
+ export class MapPermissionsUpdater extends BaseUpdater {
131
+ update({ key, value }) {
132
+ if (Utils.fieldInSchema(key)) {
133
+ Utils.setObjectValue(this._umap.permissions, key, value)
134
+ }
135
+ }
136
+
137
+ getStoredObject(metadata) {
138
+ return this._umap.permissions
139
+ }
140
+ }
141
+
142
+ export class DataLayerPermissionsUpdater extends BaseUpdater {
143
+ update({ key, value, metadata }) {
144
+ if (Utils.fieldInSchema(key)) {
145
+ Utils.setObjectValue(this.getDataLayerFromID(metadata.id), key, value)
146
+ }
147
+ }
148
+
149
+ getStoredObject(metadata) {
150
+ return this.getDataLayerFromID(metadata.id).permissions
151
+ }
129
152
  }
@@ -1,8 +1,8 @@
1
1
  import { DomEvent, DomUtil } from '../../vendors/leaflet/leaflet-src.esm.js'
2
+ import { MutatingForm } from './form/builder.js'
2
3
  import { translate } from './i18n.js'
3
4
  import ContextMenu from './ui/contextmenu.js'
4
5
  import { WithTemplate, loadTemplate } from './utils.js'
5
- import { MutatingForm } from './form/builder.js'
6
6
 
7
7
  const TEMPLATE = `
8
8
  <table>