umap-project 2.9.0b0__py3-none-any.whl → 2.9.2__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 (205) hide show
  1. umap/__init__.py +1 -1
  2. umap/admin.py +15 -2
  3. umap/locale/br/LC_MESSAGES/django.mo +0 -0
  4. umap/locale/br/LC_MESSAGES/django.po +111 -67
  5. umap/locale/cs_CZ/LC_MESSAGES/django.mo +0 -0
  6. umap/locale/cs_CZ/LC_MESSAGES/django.po +112 -67
  7. umap/locale/el/LC_MESSAGES/django.mo +0 -0
  8. umap/locale/el/LC_MESSAGES/django.po +132 -87
  9. umap/locale/en/LC_MESSAGES/django.po +11 -10
  10. umap/locale/es/LC_MESSAGES/django.mo +0 -0
  11. umap/locale/es/LC_MESSAGES/django.po +117 -71
  12. umap/locale/fr/LC_MESSAGES/django.mo +0 -0
  13. umap/locale/fr/LC_MESSAGES/django.po +11 -10
  14. umap/locale/gl/LC_MESSAGES/django.mo +0 -0
  15. umap/locale/gl/LC_MESSAGES/django.po +219 -173
  16. umap/locale/it/LC_MESSAGES/django.mo +0 -0
  17. umap/locale/it/LC_MESSAGES/django.po +145 -100
  18. umap/locale/nl/LC_MESSAGES/django.mo +0 -0
  19. umap/locale/nl/LC_MESSAGES/django.po +198 -152
  20. umap/locale/pt/LC_MESSAGES/django.mo +0 -0
  21. umap/locale/pt/LC_MESSAGES/django.po +118 -73
  22. umap/locale/zh_TW/LC_MESSAGES/django.mo +0 -0
  23. umap/locale/zh_TW/LC_MESSAGES/django.po +112 -67
  24. umap/middleware.py +30 -1
  25. umap/models.py +20 -10
  26. umap/settings/base.py +2 -1
  27. umap/static/umap/base.css +4 -1
  28. umap/static/umap/css/bar.css +32 -0
  29. umap/static/umap/css/contextmenu.css +14 -2
  30. umap/static/umap/css/form.css +5 -10
  31. umap/static/umap/css/icon.css +39 -3
  32. umap/static/umap/css/panel.css +18 -1
  33. umap/static/umap/css/popup.css +0 -1
  34. umap/static/umap/img/16-white.svg +3 -3
  35. umap/static/umap/img/16.svg +1 -1
  36. umap/static/umap/img/24-white.svg +17 -16
  37. umap/static/umap/img/24.svg +29 -18
  38. umap/static/umap/img/providers/twitter-oauth2.png +0 -0
  39. umap/static/umap/img/source/16-white.svg +4 -4
  40. umap/static/umap/img/source/16.svg +1 -1
  41. umap/static/umap/img/source/24-white.svg +20 -18
  42. umap/static/umap/img/source/24.svg +30 -19
  43. umap/static/umap/js/modules/browser.js +2 -2
  44. umap/static/umap/js/modules/caption.js +4 -4
  45. umap/static/umap/js/modules/data/features.js +80 -32
  46. umap/static/umap/js/modules/data/layer.js +37 -50
  47. umap/static/umap/js/modules/form/builder.js +23 -22
  48. umap/static/umap/js/modules/form/fields.js +13 -5
  49. umap/static/umap/js/modules/formatter.js +6 -2
  50. umap/static/umap/js/modules/help.js +17 -23
  51. umap/static/umap/js/modules/importer.js +5 -2
  52. umap/static/umap/js/modules/permissions.js +6 -2
  53. umap/static/umap/js/modules/rendering/layers/classified.js +1 -1
  54. umap/static/umap/js/modules/rendering/map.js +1 -21
  55. umap/static/umap/js/modules/rendering/ui.js +20 -38
  56. umap/static/umap/js/modules/rules.js +1 -1
  57. umap/static/umap/js/modules/saving.js +5 -0
  58. umap/static/umap/js/modules/schema.js +4 -1
  59. umap/static/umap/js/modules/sync/engine.js +39 -14
  60. umap/static/umap/js/modules/sync/updaters.js +7 -6
  61. umap/static/umap/js/modules/sync/websocket.js +48 -40
  62. umap/static/umap/js/modules/ui/bar.js +84 -0
  63. umap/static/umap/js/modules/ui/base.js +11 -0
  64. umap/static/umap/js/modules/ui/contextmenu.js +9 -2
  65. umap/static/umap/js/modules/ui/panel.js +5 -1
  66. umap/static/umap/js/modules/umap.js +85 -44
  67. umap/static/umap/js/umap.controls.js +11 -341
  68. umap/static/umap/locale/am_ET.js +17 -5
  69. umap/static/umap/locale/am_ET.json +17 -5
  70. umap/static/umap/locale/ar.js +17 -5
  71. umap/static/umap/locale/ar.json +17 -5
  72. umap/static/umap/locale/ast.js +17 -5
  73. umap/static/umap/locale/ast.json +17 -5
  74. umap/static/umap/locale/bg.js +17 -5
  75. umap/static/umap/locale/bg.json +17 -5
  76. umap/static/umap/locale/br.js +20 -15
  77. umap/static/umap/locale/br.json +20 -15
  78. umap/static/umap/locale/ca.js +8 -4
  79. umap/static/umap/locale/ca.json +8 -4
  80. umap/static/umap/locale/cs_CZ.js +8 -4
  81. umap/static/umap/locale/cs_CZ.json +8 -4
  82. umap/static/umap/locale/da.js +17 -5
  83. umap/static/umap/locale/da.json +17 -5
  84. umap/static/umap/locale/de.js +8 -4
  85. umap/static/umap/locale/de.json +8 -4
  86. umap/static/umap/locale/el.js +54 -50
  87. umap/static/umap/locale/el.json +54 -50
  88. umap/static/umap/locale/en.js +9 -4
  89. umap/static/umap/locale/en.json +9 -4
  90. umap/static/umap/locale/en_US.json +17 -5
  91. umap/static/umap/locale/es.js +13 -9
  92. umap/static/umap/locale/es.json +13 -9
  93. umap/static/umap/locale/et.js +17 -5
  94. umap/static/umap/locale/et.json +17 -5
  95. umap/static/umap/locale/eu.js +8 -4
  96. umap/static/umap/locale/eu.json +8 -4
  97. umap/static/umap/locale/fa_IR.js +8 -4
  98. umap/static/umap/locale/fa_IR.json +8 -4
  99. umap/static/umap/locale/fi.js +17 -5
  100. umap/static/umap/locale/fi.json +17 -5
  101. umap/static/umap/locale/fr.js +9 -4
  102. umap/static/umap/locale/fr.json +9 -4
  103. umap/static/umap/locale/gl.js +13 -9
  104. umap/static/umap/locale/gl.json +13 -9
  105. umap/static/umap/locale/he.js +17 -5
  106. umap/static/umap/locale/he.json +17 -5
  107. umap/static/umap/locale/hr.js +17 -5
  108. umap/static/umap/locale/hr.json +17 -5
  109. umap/static/umap/locale/hu.js +8 -4
  110. umap/static/umap/locale/hu.json +8 -4
  111. umap/static/umap/locale/id.js +17 -5
  112. umap/static/umap/locale/id.json +17 -5
  113. umap/static/umap/locale/is.js +17 -5
  114. umap/static/umap/locale/is.json +17 -5
  115. umap/static/umap/locale/it.js +31 -27
  116. umap/static/umap/locale/it.json +31 -27
  117. umap/static/umap/locale/ja.js +17 -5
  118. umap/static/umap/locale/ja.json +17 -5
  119. umap/static/umap/locale/ko.js +17 -5
  120. umap/static/umap/locale/ko.json +17 -5
  121. umap/static/umap/locale/lt.js +17 -5
  122. umap/static/umap/locale/lt.json +17 -5
  123. umap/static/umap/locale/ms.js +8 -4
  124. umap/static/umap/locale/ms.json +8 -4
  125. umap/static/umap/locale/nl.js +132 -127
  126. umap/static/umap/locale/nl.json +132 -127
  127. umap/static/umap/locale/no.js +17 -5
  128. umap/static/umap/locale/no.json +17 -5
  129. umap/static/umap/locale/pl.js +8 -4
  130. umap/static/umap/locale/pl.json +8 -4
  131. umap/static/umap/locale/pl_PL.json +17 -5
  132. umap/static/umap/locale/pt.js +38 -33
  133. umap/static/umap/locale/pt.json +38 -33
  134. umap/static/umap/locale/pt_BR.js +17 -5
  135. umap/static/umap/locale/pt_BR.json +17 -5
  136. umap/static/umap/locale/pt_PT.js +8 -4
  137. umap/static/umap/locale/pt_PT.json +8 -4
  138. umap/static/umap/locale/ro.js +17 -5
  139. umap/static/umap/locale/ro.json +17 -5
  140. umap/static/umap/locale/ru.js +17 -5
  141. umap/static/umap/locale/ru.json +17 -5
  142. umap/static/umap/locale/sk_SK.js +17 -5
  143. umap/static/umap/locale/sk_SK.json +17 -5
  144. umap/static/umap/locale/sl.js +17 -5
  145. umap/static/umap/locale/sl.json +17 -5
  146. umap/static/umap/locale/sr.js +17 -5
  147. umap/static/umap/locale/sr.json +17 -5
  148. umap/static/umap/locale/sv.js +17 -5
  149. umap/static/umap/locale/sv.json +17 -5
  150. umap/static/umap/locale/th_TH.js +8 -4
  151. umap/static/umap/locale/th_TH.json +8 -4
  152. umap/static/umap/locale/tr.js +17 -5
  153. umap/static/umap/locale/tr.json +17 -5
  154. umap/static/umap/locale/uk_UA.js +17 -5
  155. umap/static/umap/locale/uk_UA.json +17 -5
  156. umap/static/umap/locale/vi.js +17 -5
  157. umap/static/umap/locale/vi.json +17 -5
  158. umap/static/umap/locale/vi_VN.json +17 -5
  159. umap/static/umap/locale/zh.js +17 -5
  160. umap/static/umap/locale/zh.json +17 -5
  161. umap/static/umap/locale/zh_CN.json +17 -5
  162. umap/static/umap/locale/zh_TW.Big5.json +17 -5
  163. umap/static/umap/locale/zh_TW.js +14 -10
  164. umap/static/umap/locale/zh_TW.json +14 -10
  165. umap/static/umap/map.css +17 -68
  166. umap/static/umap/nav.css +4 -0
  167. umap/static/umap/vars.css +1 -0
  168. umap/static/umap/vendors/dompurify/purify.es.js +138 -354
  169. umap/static/umap/vendors/dompurify/purify.es.mjs.map +1 -1
  170. umap/static/umap/vendors/editable/Leaflet.Editable.js +1 -0
  171. umap/sync/app.py +19 -13
  172. umap/sync/payloads.py +8 -1
  173. umap/templates/auth/user_form.html +2 -2
  174. umap/templates/umap/content_footer.html +1 -1
  175. umap/templates/umap/css.html +0 -2
  176. umap/templates/umap/js.html +0 -1
  177. umap/templates/umap/messages.html +5 -1
  178. umap/templates/umap/search_bar.html +1 -0
  179. umap/tests/integration/test_anonymous_owned_map.py +2 -2
  180. umap/tests/integration/test_basics.py +2 -5
  181. umap/tests/integration/test_categorized_layer.py +4 -8
  182. umap/tests/integration/test_choropleth.py +1 -1
  183. umap/tests/integration/test_conditional_rules.py +3 -3
  184. umap/tests/integration/test_draw_polygon.py +11 -19
  185. umap/tests/integration/test_draw_polyline.py +6 -14
  186. umap/tests/integration/test_edit_datalayer.py +10 -10
  187. umap/tests/integration/test_edit_map.py +27 -1
  188. umap/tests/integration/test_edit_marker.py +5 -5
  189. umap/tests/integration/test_edit_polygon.py +5 -5
  190. umap/tests/integration/test_features_id_generation.py +2 -6
  191. umap/tests/integration/test_import.py +93 -29
  192. umap/tests/integration/test_owned_map.py +1 -1
  193. umap/tests/integration/test_save.py +2 -2
  194. umap/tests/integration/test_tableeditor.py +7 -7
  195. umap/tests/integration/test_view_marker.py +10 -0
  196. umap/tests/integration/test_websocket_sync.py +128 -32
  197. umap/utils.py +4 -1
  198. umap/views.py +1 -10
  199. {umap_project-2.9.0b0.dist-info → umap_project-2.9.2.dist-info}/METADATA +13 -13
  200. {umap_project-2.9.0b0.dist-info → umap_project-2.9.2.dist-info}/RECORD +203 -205
  201. umap/static/umap/vendors/toolbar/leaflet.toolbar.css +0 -1
  202. umap/static/umap/vendors/toolbar/leaflet.toolbar.js +0 -1
  203. {umap_project-2.9.0b0.dist-info → umap_project-2.9.2.dist-info}/WHEEL +0 -0
  204. {umap_project-2.9.0b0.dist-info → umap_project-2.9.2.dist-info}/entry_points.txt +0 -0
  205. {umap_project-2.9.0b0.dist-info → umap_project-2.9.2.dist-info}/licenses/LICENSE +0 -0
@@ -63,7 +63,7 @@ export class Form extends Utils.WithEvents {
63
63
  try {
64
64
  value = value[sub]
65
65
  } catch {
66
- console.log(field)
66
+ console.debug(field)
67
67
  }
68
68
  }
69
69
  return value
@@ -142,49 +142,50 @@ export class MutatingForm extends Form {
142
142
  slugKey: 'PropertyInput',
143
143
  labelKey: 'PropertyInput',
144
144
  }
145
- for (const [key, schema] of Object.entries(SCHEMA)) {
146
- if (schema.type === Boolean) {
147
- if (schema.nullable) schema.handler = 'NullableChoices'
148
- else schema.handler = 'Switch'
149
- } else if (schema.type === 'Text') {
150
- schema.handler = 'Textarea'
151
- } else if (schema.type === Number) {
152
- if (schema.step) schema.handler = 'Range'
153
- else schema.handler = 'IntInput'
154
- } else if (schema.choices) {
155
- const text_length = schema.choices.reduce(
145
+ for (const [key, defaults] of Object.entries(SCHEMA)) {
146
+ const properties = Object.assign({}, defaults)
147
+ if (properties.type === Boolean) {
148
+ if (properties.nullable) properties.handler = 'NullableChoices'
149
+ else properties.handler = 'Switch'
150
+ } else if (properties.type === 'Text') {
151
+ properties.handler = 'Textarea'
152
+ } else if (properties.type === Number) {
153
+ if (properties.step) properties.handler = 'Range'
154
+ else properties.handler = 'IntInput'
155
+ } else if (properties.choices) {
156
+ const text_length = properties.choices.reduce(
156
157
  (acc, [_, label]) => acc + label.length,
157
158
  0
158
159
  )
159
160
  // Try to be smart and use MultiChoice only
160
161
  // for choices where labels are shorts…
161
162
  if (text_length < 40) {
162
- schema.handler = 'MultiChoice'
163
+ properties.handler = 'MultiChoice'
163
164
  } else {
164
- schema.handler = 'Select'
165
- schema.selectOptions = schema.choices
165
+ properties.handler = 'Select'
166
+ properties.selectOptions = properties.choices
166
167
  }
167
168
  } else {
168
169
  switch (key) {
169
170
  case 'color':
170
171
  case 'fillColor':
171
- schema.handler = 'ColorPicker'
172
+ properties.handler = 'ColorPicker'
172
173
  break
173
174
  case 'iconUrl':
174
- schema.handler = 'IconUrl'
175
+ properties.handler = 'IconUrl'
175
176
  break
176
177
  case 'licence':
177
- schema.handler = 'LicenceChooser'
178
+ properties.handler = 'LicenceChooser'
178
179
  break
179
180
  }
180
181
  }
181
182
 
182
183
  if (customHandlers[key]) {
183
- schema.handler = customHandlers[key]
184
+ properties.handler = customHandlers[key]
184
185
  }
185
186
  // Input uses this key for its type attribute
186
- delete schema.type
187
- this.defaultProperties[key] = schema
187
+ delete properties.type
188
+ this.defaultProperties[key] = properties
188
189
  }
189
190
  }
190
191
 
@@ -202,7 +203,7 @@ export class MutatingForm extends Form {
202
203
  getTemplate(helper) {
203
204
  let template
204
205
  if (helper.properties.inheritable) {
205
- const extraClassName = helper.get(true) === undefined ? ' undefined' : ''
206
+ const extraClassName = this.getter(helper.field) === undefined ? ' undefined' : ''
206
207
  template = `
207
208
  <div class="umap-field-${helper.name} formbox inheritable${extraClassName}">
208
209
  <div class="header" data-ref=header>
@@ -80,11 +80,17 @@ class BaseElement {
80
80
  this.input.value = ''
81
81
  }
82
82
 
83
- get(own) {
84
- if (!this.properties.inheritable || own) return this.builder.getter(this.field)
83
+ get() {
84
+ let value
85
85
  const path = this.field.split('.')
86
86
  const key = path[path.length - 1]
87
- return this.obj.getOption(key) || SCHEMA[key]?.default
87
+ if (!this.properties.inheritable) {
88
+ value = this.builder.getter(this.field)
89
+ } else {
90
+ value = this.obj.getOption(key)
91
+ }
92
+ if (value === undefined) return SCHEMA[key]?.default
93
+ return value
88
94
  }
89
95
 
90
96
  toHTML() {
@@ -1164,7 +1170,7 @@ Fields.MultiChoice = class extends BaseElement {
1164
1170
 
1165
1171
  Fields.TernaryChoices = class extends Fields.MultiChoice {
1166
1172
  getDefault() {
1167
- return 'null'
1173
+ return null
1168
1174
  }
1169
1175
 
1170
1176
  toJS() {
@@ -1195,7 +1201,7 @@ Fields.NullableChoices = class extends Fields.TernaryChoices {
1195
1201
  this.properties.choices || [
1196
1202
  [true, translate('always')],
1197
1203
  [false, translate('never')],
1198
- ['null', translate('hidden')],
1204
+ [null, translate('hidden')],
1199
1205
  ]
1200
1206
  )
1201
1207
  }
@@ -1251,6 +1257,7 @@ Fields.Range = class extends Fields.FloatInput {
1251
1257
 
1252
1258
  Fields.ManageOwner = class extends BaseElement {
1253
1259
  build() {
1260
+ super.build()
1254
1261
  const options = {
1255
1262
  className: 'edit-owner',
1256
1263
  on_select: L.bind(this.onSelect, this),
@@ -1281,6 +1288,7 @@ Fields.ManageOwner = class extends BaseElement {
1281
1288
 
1282
1289
  Fields.ManageEditors = class extends BaseElement {
1283
1290
  build() {
1291
+ super.build()
1284
1292
  const options = {
1285
1293
  className: 'edit-editors',
1286
1294
  on_select: L.bind(this.onSelect, this),
@@ -103,8 +103,12 @@ export class Formatter {
103
103
  message: err[0].message,
104
104
  })
105
105
  }
106
- Alert.error(message, 10000)
107
- console.debug(err)
106
+ if (str.split(/\r\n|\r|\n/).length <= 2) {
107
+ // Seems like a blank CSV, let's not warn
108
+ console.debug(err)
109
+ } else {
110
+ Alert.error(message, 10000)
111
+ }
108
112
  }
109
113
  if (result?.features.length) {
110
114
  callback(result)
@@ -206,13 +206,23 @@ export default class Help {
206
206
 
207
207
  // Special dynamic case. Do we still think this dialog is useful?
208
208
  showGetStarted() {
209
- const container = DomUtil.add('div')
210
- DomUtil.createTitle(container, translate('Where do we go from here?'))
211
- DomUtil.element({
212
- tagName: 'div',
213
- className: 'umap-help-entry',
214
- parent: container,
215
- }).appendChild(this._buildEditEntry())
209
+ const [container, { ul }] = Utils.loadTemplateWithRefs(`
210
+ <div>
211
+ <h3><i class="icon icon-16 icon-help"></i>${translate('Where do we go from here?')}</h3>
212
+ <ul data-ref=ul class="umap-getstarted"></ul>
213
+ </div>
214
+ `)
215
+ const elements = document.querySelectorAll('[data-getstarted]')
216
+ for (const el of elements) {
217
+ const [node, { button }] = Utils.loadTemplateWithRefs(
218
+ `<li><button data-ref=button type="button" title="${el.title}">${el.innerHTML}${el.title}</button></li>`
219
+ )
220
+ ul.appendChild(node)
221
+ button.addEventListener('click', () => {
222
+ el.click()
223
+ this.dialog.close()
224
+ })
225
+ }
216
226
  this.dialog.open({ template: container })
217
227
  }
218
228
 
@@ -233,22 +243,6 @@ export default class Help {
233
243
  }
234
244
  }
235
245
  }
236
-
237
- _buildEditEntry() {
238
- const container = DomUtil.create('div', '')
239
- const actionsContainer = DomUtil.create('ul', 'umap-edit-actions', container)
240
- const addAction = (action) => {
241
- const actionContainer = DomUtil.add('li', '', actionsContainer)
242
- DomUtil.add('i', action.options.className, actionContainer)
243
- DomUtil.add('span', '', actionContainer, action.options.tooltip)
244
- DomEvent.on(actionContainer, 'click', action.addHooks, action)
245
- DomEvent.on(actionContainer, 'click', this.dialog.close, this.dialog)
246
- }
247
- for (const action of Object.values(Help.MENU_ACTIONS)) {
248
- addAction(action)
249
- }
250
- return container
251
- }
252
246
  }
253
247
 
254
248
  Help.MENU_ACTIONS = {}
@@ -10,7 +10,7 @@ import Dialog from './ui/dialog.js'
10
10
  import * as Utils from './utils.js'
11
11
 
12
12
  const TEMPLATE = `
13
- <div class="umap-upload">
13
+ <div class="umap-import">
14
14
  <h3><i class="icon icon-16 icon-upload"></i><span>${translate('Import data')}</span></h3>
15
15
  <fieldset class="formbox">
16
16
  <legend class="counter">${translate('Choose data')}</legend>
@@ -261,7 +261,10 @@ export default class Importer extends Utils.WithTemplate {
261
261
 
262
262
  open() {
263
263
  if (!this.container) this.build()
264
- const onLoad = this._umap.editPanel.open({ content: this.container })
264
+ const onLoad = this._umap.editPanel.open({
265
+ content: this.container,
266
+ highlight: 'import',
267
+ })
265
268
  onLoad.then(() => this.onLoad())
266
269
  }
267
270
 
@@ -166,12 +166,16 @@ export class MapPermissions extends ServerStored {
166
166
  Alert.info(translate('Please save the map first'))
167
167
  return
168
168
  }
169
- const container = DomUtil.create('div', 'permissions-panel')
169
+ const container = DomUtil.create('div', 'umap-edit-permissions')
170
170
  DomUtil.createTitle(container, translate('Update permissions'), 'icon-key')
171
171
  if (this.isAnonymousMap()) this._editAnonymous(container)
172
172
  else this._editWithOwner(container)
173
173
  this._editDatalayers(container)
174
- this._umap.editPanel.open({ content: container, className: 'dark' })
174
+ this._umap.editPanel.open({
175
+ content: container,
176
+ className: 'dark',
177
+ highlight: 'permissions',
178
+ })
175
179
  }
176
180
 
177
181
  async attach() {
@@ -75,7 +75,7 @@ const ClassifiedMixin = {
75
75
  },
76
76
 
77
77
  renderLegend: function (container) {
78
- if (!this.datalayer.hasDataLoaded()) return
78
+ if (!this.datalayer.isLoaded()) return
79
79
  const parent = DomUtil.create('ul', '', container)
80
80
  const items = this.getLegendItems()
81
81
  for (const [color, label] of items) {
@@ -40,20 +40,6 @@ const ControlsMixin = {
40
40
 
41
41
  if (this._umap.hasEditMode() && !this.options.noControl) {
42
42
  new U.EditControl(this).addTo(this)
43
-
44
- new U.DrawToolbar({ map: this }).addTo(this)
45
- const editActions = [
46
- U.EditCaptionAction,
47
- U.ImportAction,
48
- U.EditLayersAction,
49
- U.ChangeTileLayerAction,
50
- U.UpdateExtentAction,
51
- U.UpdatePermsAction,
52
- U.EditPropertiesAction,
53
- ]
54
- if (this.options.editMode === 'advanced') {
55
- new U.SettingsToolbar({ actions: editActions }).addTo(this)
56
- }
57
43
  }
58
44
  this._controls.zoom = new Control.Zoom({
59
45
  zoomInTitle: translate('Zoom in'),
@@ -231,7 +217,7 @@ const ManageTilelayerMixin = {
231
217
  }
232
218
  },
233
219
 
234
- updateTileLayers: function () {
220
+ editTileLayers: function () {
235
221
  if (this._controls.tilelayersChooser) {
236
222
  this._controls.tilelayersChooser.openSwitcher({ edit: true })
237
223
  }
@@ -264,7 +250,6 @@ export const LeafletMap = BaseMap.extend({
264
250
  DomEvent.on(document.body, 'dataload', (event) =>
265
251
  this.fire('dataload', event.detail)
266
252
  )
267
- this.on('click', this.closeInplaceToolbar)
268
253
  }
269
254
 
270
255
  this.on('baselayerchange', (e) => {
@@ -298,11 +283,6 @@ export const LeafletMap = BaseMap.extend({
298
283
  this.handleLimitBounds()
299
284
  },
300
285
 
301
- closeInplaceToolbar: function () {
302
- const toolbar = this._toolbars[L.Toolbar.Popup._toolbar_class_id]
303
- if (toolbar) toolbar.remove()
304
- },
305
-
306
286
  latLng: (a, b, c) => {
307
287
  // manage geojson case and call original method
308
288
  if (!(a instanceof L.LatLng) && a.coordinates) {
@@ -29,12 +29,9 @@ const FeatureMixin = {
29
29
 
30
30
  onRemove: function (map) {
31
31
  this.parentClass.prototype.onRemove.call(this, map)
32
- if (map.editedFeature === this.feature) {
33
- this.feature._marked_for_deletion = true
32
+ if (map._umap.editedFeature === this.feature) {
34
33
  this.feature.endEdit()
35
- if (map.editedFeature === this.feature) {
36
- map.editPanel.close()
37
- }
34
+ map._umap.editPanel.close()
38
35
  }
39
36
  },
40
37
 
@@ -47,6 +44,7 @@ const FeatureMixin = {
47
44
  addInteractions: function () {
48
45
  this.on('contextmenu editable:vertex:contextmenu', this.onContextMenu)
49
46
  this.on('click', this.onClick)
47
+ this.on('editable:edited', this.onCommit)
50
48
  },
51
49
 
52
50
  onClick: function (event) {
@@ -59,15 +57,13 @@ const FeatureMixin = {
59
57
  if (event.originalEvent.ctrlKey || event.originalEvent.metaKey) {
60
58
  this.feature.datalayer.edit(event)
61
59
  } else {
62
- if (this.feature._toggleEditing) this.feature._toggleEditing(event)
63
- else this.feature.edit(event)
60
+ this.feature.toggleEditing(event)
64
61
  }
65
62
  } else if (!this._map.editTools?.drawing()) {
66
- new L.Toolbar.Popup(event.latlng, {
67
- className: 'leaflet-inplace-toolbar',
68
- anchor: this.getPopupToolbarAnchor(),
69
- actions: this.feature.getInplaceToolbarActions(event),
70
- }).addTo(this._map, this.feature, event.latlng)
63
+ this._map._umap.editContextmenu.open(
64
+ event.originalEvent,
65
+ this.feature.getInplaceEditMenu(event)
66
+ )
71
67
  }
72
68
  }
73
69
  DomEvent.stop(event)
@@ -104,8 +100,6 @@ const FeatureMixin = {
104
100
  this.feature.pullGeometry(false)
105
101
  this.feature.onCommit()
106
102
  },
107
-
108
- getPopupToolbarAnchor: () => [0, 0],
109
103
  }
110
104
 
111
105
  const PointMixin = {
@@ -118,9 +112,8 @@ const PointMixin = {
118
112
  this.on('dragend', (event) => {
119
113
  this.isDirty = true
120
114
  this.feature.edit(event)
121
- this.feature.pullGeometry()
115
+ this.feature.pullGeometry(false)
122
116
  })
123
- this.on('editable:drawing:commit', this.onCommit)
124
117
  if (!this.feature.isReadOnly()) this.on('mouseover', this._enableDragging)
125
118
  this.on('mouseout', this._onMouseOut)
126
119
  },
@@ -248,10 +241,6 @@ export const LeafletMarker = Marker.extend({
248
241
  this._redraw()
249
242
  this._resetZIndex()
250
243
  },
251
-
252
- getPopupToolbarAnchor: function () {
253
- return this.options.icon.options.popupAnchor
254
- },
255
244
  })
256
245
 
257
246
  const PathMixin = {
@@ -267,6 +256,8 @@ const PathMixin = {
267
256
  },
268
257
 
269
258
  makeGeometryEditable: function () {
259
+ // Feature has been removed since then?
260
+ if (!this._map) return
270
261
  if (this._map._umap.editedFeature !== this.feature) {
271
262
  this.disableEdit()
272
263
  return
@@ -285,13 +276,17 @@ const PathMixin = {
285
276
 
286
277
  addInteractions: function () {
287
278
  FeatureMixin.addInteractions.call(this)
288
- this.on('editable:disable', this.onCommit)
289
279
  this.on('mouseover', this._onMouseOver)
290
280
  this.on('drag editable:drag', this._onDrag)
291
281
  this.on('popupopen', this.highlightPath)
292
282
  this.on('popupclose', this._redraw)
293
283
  },
294
284
 
285
+ bindTooltip: function (content, options) {
286
+ options.sticky = !options.permanent
287
+ this.parentClass.prototype.bindTooltip.call(this, content, options)
288
+ },
289
+
295
290
  highlightPath: function () {
296
291
  this.parentClass.prototype.setStyle.call(this, {
297
292
  fillOpacity: Math.sqrt(this.feature.getDynamicOption('fillOpacity', 1.0)),
@@ -335,13 +330,11 @@ const PathMixin = {
335
330
  this.resetTooltip()
336
331
  },
337
332
 
338
- getVertexActions: () => [U.DeleteVertexAction],
339
-
340
333
  onVertexRawClick: function (event) {
341
- new L.Toolbar.Popup(event.latlng, {
342
- className: 'leaflet-inplace-toolbar',
343
- actions: this.getVertexActions(event),
344
- }).addTo(this._map, this, event.latlng, event.vertex)
334
+ this._map._umap.editContextmenu.open(
335
+ event.originalEvent,
336
+ this.feature.getInplaceEditVertexMenu(event)
337
+ )
345
338
  },
346
339
 
347
340
  isolateShape: function (atLatLng) {
@@ -377,17 +370,6 @@ export const LeafletPolyline = Polyline.extend({
377
370
 
378
371
  getClass: () => LeafletPolyline,
379
372
 
380
- getVertexActions: function (event) {
381
- const actions = PathMixin.getVertexActions.call(this, event)
382
- const index = event.vertex.getIndex()
383
- if (index === 0 || index === event.vertex.getLastIndex()) {
384
- actions.push(U.ContinueLineAction)
385
- } else {
386
- actions.push(U.SplitLineAction)
387
- }
388
- return actions
389
- },
390
-
391
373
  getMeasure: function (shape) {
392
374
  let shapes
393
375
  if (shape) {
@@ -147,7 +147,7 @@ class Rule {
147
147
  .map((str) => `${value}${str || ''}`)
148
148
  }
149
149
  })
150
- this._umap.editPanel.open({ content: container })
150
+ this._umap.editPanel.open({ content: container, highlight: 'settings' })
151
151
  }
152
152
 
153
153
  renderToolbox(row) {
@@ -10,6 +10,11 @@ export async function save() {
10
10
  }
11
11
  }
12
12
 
13
+ export function clear() {
14
+ _queue.clear()
15
+ onUpdate()
16
+ }
17
+
13
18
  function add(obj) {
14
19
  _queue.add(obj)
15
20
  onUpdate()
@@ -278,6 +278,7 @@ export const SCHEMA = {
278
278
  impacts: ['ui'],
279
279
  nullable: true,
280
280
  label: translate('Display the locate control'),
281
+ default: null,
281
282
  },
282
283
  longCredit: {
283
284
  type: 'Text',
@@ -290,6 +291,7 @@ export const SCHEMA = {
290
291
  impacts: ['ui'],
291
292
  nullable: true,
292
293
  label: translate('Display the measure control'),
294
+ default: null,
293
295
  },
294
296
  mask: {
295
297
  type: Boolean,
@@ -450,7 +452,7 @@ export const SCHEMA = {
450
452
  choices: [
451
453
  [true, translate('always')],
452
454
  [false, translate('never')],
453
- ['null', translate('on hover')],
455
+ [null, translate('on hover')],
454
456
  ],
455
457
  },
456
458
  slideshow: {
@@ -507,6 +509,7 @@ export const SCHEMA = {
507
509
  impacts: ['ui'],
508
510
  nullable: true,
509
511
  label: translate('Display the tile layers control'),
512
+ default: null,
510
513
  },
511
514
  toZoom: {
512
515
  type: Number,
@@ -2,6 +2,7 @@ import * as Utils from '../utils.js'
2
2
  import { HybridLogicalClock } from './hlc.js'
3
3
  import { DataLayerUpdater, FeatureUpdater, MapUpdater } from './updaters.js'
4
4
  import { WebSocketTransport } from './websocket.js'
5
+ import * as SaveManager from '../saving.js'
5
6
 
6
7
  // Start reconnecting after 2 seconds, then double the delay each time
7
8
  // maxing out at 32 seconds.
@@ -65,27 +66,35 @@ export class SyncEngine {
65
66
  this.peerId = Utils.generateId()
66
67
  }
67
68
 
69
+ get isOpen() {
70
+ return this.transport?.isOpen
71
+ }
72
+
68
73
  async authenticate() {
74
+ if (this.isOpen) return
69
75
  const websocketTokenURI = this._umap.urls.get('map_websocket_auth_token', {
70
76
  map_id: this._umap.id,
71
77
  })
72
78
 
73
79
  const [response, _, error] = await this._umap.server.get(websocketTokenURI)
74
- if (!error) {
75
- this.start(response.token)
80
+ if (error) {
81
+ this.reconnect()
82
+ return
76
83
  }
84
+ await this.start(response.token)
77
85
  }
78
86
 
79
- start(authToken) {
87
+ async start(authToken) {
80
88
  const path = this._umap.urls.get('ws_sync', { map_id: this._umap.id })
81
89
  const protocol = window.location.protocol === 'http:' ? 'ws:' : 'wss:'
82
- this.transport = new WebSocketTransport(
90
+ this.transport = new WebSocketTransport(this)
91
+ await this.transport.connect(
83
92
  `${protocol}//${window.location.host}${path}`,
84
93
  authToken,
85
- this,
86
94
  this.peerId,
87
95
  this._umap.properties.user?.name
88
96
  )
97
+ this.onConnection()
89
98
  }
90
99
 
91
100
  stop() {
@@ -106,11 +115,11 @@ export class SyncEngine {
106
115
  this.websocketConnected = false
107
116
  this.updaters.map.update({ key: 'numberOfConnectedPeers' })
108
117
 
109
- this._reconnectTimeout = setTimeout(() => {
118
+ this._reconnectTimeout = setTimeout(async () => {
110
119
  if (this._reconnectDelay < MAX_RECONNECT_DELAY) {
111
120
  this._reconnectDelay = this._reconnectDelay * RECONNECT_DELAY_FACTOR
112
121
  }
113
- this.authenticate()
122
+ await this.authenticate()
114
123
  }, this._reconnectDelay)
115
124
  }
116
125
  upsert(subject, metadata, value) {
@@ -125,6 +134,16 @@ export class SyncEngine {
125
134
  this._send({ verb: 'delete', subject, metadata, key })
126
135
  }
127
136
 
137
+ saved() {
138
+ if (this.offline) return
139
+ if (this.transport) {
140
+ this.transport.send('SavedMessage', {
141
+ sender: this.peerId,
142
+ lastKnownHLC: this._operations.getLastKnownHLC(),
143
+ })
144
+ }
145
+ }
146
+
128
147
  _send(inputMessage) {
129
148
  const message = this._operations.addLocal(inputMessage)
130
149
 
@@ -168,6 +187,8 @@ export class SyncEngine {
168
187
  } else if (payload.message.verb === 'ListOperationsResponse') {
169
188
  this.onListOperationsResponse(payload)
170
189
  }
190
+ } else if (kind === 'SavedMessage') {
191
+ this.onSavedMessage(payload)
171
192
  } else {
172
193
  throw new Error(`Received unknown message from the websocket server: ${kind}`)
173
194
  }
@@ -182,6 +203,7 @@ export class SyncEngine {
182
203
  */
183
204
  onOperationMessage(payload) {
184
205
  if (payload.sender === this.peerId) return
206
+ debug('received operation', payload)
185
207
  this._operations.storeRemoteOperations([payload])
186
208
  this._applyOperation(payload)
187
209
  }
@@ -245,7 +267,7 @@ export class SyncEngine {
245
267
  * @param {*} operations The list of (encoded operations)
246
268
  */
247
269
  onListOperationsResponse({ sender, message }) {
248
- debug(`received operations from peer ${sender}`, message.operations)
270
+ debug(`received operations list from peer ${sender}`, message.operations)
249
271
 
250
272
  if (message.operations.length === 0) return
251
273
 
@@ -280,6 +302,13 @@ export class SyncEngine {
280
302
  // Else: apply
281
303
  }
282
304
 
305
+ onSavedMessage({ sender, lastKnownHLC }) {
306
+ debug(`received saved message from peer ${sender}`, lastKnownHLC)
307
+ if (lastKnownHLC === this._operations.getLastKnownHLC() && SaveManager.isDirty) {
308
+ SaveManager.clear()
309
+ }
310
+ }
311
+
283
312
  /**
284
313
  * Send a message to another peer (via the transport layer)
285
314
  *
@@ -350,7 +379,7 @@ export class Operations {
350
379
  }
351
380
 
352
381
  /**
353
- * Tick the clock and add store the passed message in the operations list.
382
+ * Tick the clock and store the passed message in the operations list.
354
383
  *
355
384
  * @param {*} inputMessage
356
385
  * @returns {*} clock-aware message
@@ -479,11 +508,7 @@ export class Operations {
479
508
  * @return {bool} true if the two operations share the same context.
480
509
  */
481
510
  static haveSameContext(local, remote) {
482
- const shouldCheckKey =
483
- local.hasOwnProperty('key') &&
484
- remote.hasOwnProperty('key') &&
485
- typeof local.key !== 'undefined' &&
486
- typeof remote.key !== 'undefined'
511
+ const shouldCheckKey = local.key !== undefined && remote.key !== undefined
487
512
 
488
513
  return (
489
514
  Utils.deepEqual(local.subject, remote.subject) &&
@@ -54,10 +54,11 @@ export class MapUpdater extends BaseUpdater {
54
54
  export class DataLayerUpdater extends BaseUpdater {
55
55
  upsert({ value }) {
56
56
  // Upsert only happens when a new datalayer is created.
57
- const datalayer = this._umap.createDataLayer(value, false)
58
- // Prevent the layer to get data from the server, as it will get it
59
- // from the sync.
60
- datalayer._loaded = true
57
+ try {
58
+ this.getDataLayerFromID(value.id)
59
+ } catch {
60
+ this._umap.createDataLayer(value, false)
61
+ }
61
62
  }
62
63
 
63
64
  update({ key, metadata, value }) {
@@ -92,7 +93,7 @@ export class FeatureUpdater extends BaseUpdater {
92
93
  upsert({ metadata, value }) {
93
94
  const { id, layerId } = metadata
94
95
  const datalayer = this.getDataLayerFromID(layerId)
95
- const feature = this.getFeatureFromMetadata(metadata, value)
96
+ const feature = this.getFeatureFromMetadata(metadata)
96
97
 
97
98
  if (feature) {
98
99
  feature.geometry = value.geometry
@@ -109,7 +110,7 @@ export class FeatureUpdater extends BaseUpdater {
109
110
  return
110
111
  }
111
112
  if (key === 'geometry') {
112
- const feature = this.getFeatureFromMetadata(metadata, value)
113
+ const feature = this.getFeatureFromMetadata(metadata)
113
114
  feature.geometry = value
114
115
  } else {
115
116
  this.updateObjectValue(feature, key, value)