umap-project 2.8.2__py3-none-any.whl → 2.9.0b0__py3-none-any.whl

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

Potentially problematic release.


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

Files changed (157) hide show
  1. umap/__init__.py +1 -1
  2. umap/asgi.py +12 -7
  3. umap/context_processors.py +1 -0
  4. umap/locale/en/LC_MESSAGES/django.po +102 -59
  5. umap/locale/fr/LC_MESSAGES/django.mo +0 -0
  6. umap/locale/fr/LC_MESSAGES/django.po +105 -61
  7. umap/management/commands/empty_trash.py +12 -1
  8. umap/migrations/0026_datalayer_modified_at_datalayer_share_status.py +26 -0
  9. umap/models.py +23 -3
  10. umap/settings/base.py +4 -1
  11. umap/static/umap/base.css +1 -1
  12. umap/static/umap/content.css +2 -22
  13. umap/static/umap/css/bar.css +7 -10
  14. umap/static/umap/css/form.css +28 -29
  15. umap/static/umap/css/icon.css +8 -2
  16. umap/static/umap/css/panel.css +2 -1
  17. umap/static/umap/css/tooltip.css +33 -31
  18. umap/static/umap/img/16-white.svg +2 -0
  19. umap/static/umap/img/16.svg +1 -1
  20. umap/static/umap/img/providers/bitbucket.png +0 -0
  21. umap/static/umap/img/providers/github.png +0 -0
  22. umap/static/umap/img/providers/keycloak.png +0 -0
  23. umap/static/umap/img/providers/openstreetmap-oauth2.png +0 -0
  24. umap/static/umap/img/providers/twitter-oauth2.png +0 -0
  25. umap/static/umap/img/source/16-white.svg +3 -1
  26. umap/static/umap/img/source/16.svg +1 -1
  27. umap/static/umap/js/components/alerts/alert.js +4 -1
  28. umap/static/umap/js/modules/browser.js +6 -6
  29. umap/static/umap/js/modules/caption.js +30 -7
  30. umap/static/umap/js/modules/data/features.js +21 -24
  31. umap/static/umap/js/modules/data/layer.js +71 -33
  32. umap/static/umap/js/modules/form/builder.js +241 -0
  33. umap/static/umap/js/modules/form/fields.js +1338 -0
  34. umap/static/umap/js/modules/formatter.js +5 -8
  35. umap/static/umap/js/modules/help.js +3 -1
  36. umap/static/umap/js/modules/importer.js +1 -1
  37. umap/static/umap/js/modules/permissions.js +5 -4
  38. umap/static/umap/js/modules/rendering/icon.js +5 -1
  39. umap/static/umap/js/modules/rendering/layers/classified.js +11 -7
  40. umap/static/umap/js/modules/rendering/layers/cluster.js +11 -1
  41. umap/static/umap/js/modules/rendering/map.js +0 -2
  42. umap/static/umap/js/modules/rules.js +2 -1
  43. umap/static/umap/js/modules/schema.js +5 -6
  44. umap/static/umap/js/modules/share.js +3 -3
  45. umap/static/umap/js/modules/sync/engine.js +18 -13
  46. umap/static/umap/js/modules/sync/updaters.js +8 -0
  47. umap/static/umap/js/modules/sync/websocket.js +10 -5
  48. umap/static/umap/js/modules/tableeditor.js +3 -2
  49. umap/static/umap/js/modules/ui/bar.js +17 -9
  50. umap/static/umap/js/modules/ui/base.js +7 -24
  51. umap/static/umap/js/modules/ui/tooltip.js +19 -11
  52. umap/static/umap/js/modules/umap.js +36 -24
  53. umap/static/umap/js/modules/utils.js +196 -12
  54. umap/static/umap/js/umap.controls.js +0 -12
  55. umap/static/umap/locale/br.js +21 -13
  56. umap/static/umap/locale/br.json +21 -13
  57. umap/static/umap/locale/ca.js +12 -4
  58. umap/static/umap/locale/ca.json +12 -4
  59. umap/static/umap/locale/cs_CZ.js +10 -4
  60. umap/static/umap/locale/cs_CZ.json +10 -4
  61. umap/static/umap/locale/de.js +12 -4
  62. umap/static/umap/locale/de.json +12 -4
  63. umap/static/umap/locale/el.js +12 -4
  64. umap/static/umap/locale/el.json +12 -4
  65. umap/static/umap/locale/en.js +9 -4
  66. umap/static/umap/locale/en.json +9 -4
  67. umap/static/umap/locale/es.js +20 -12
  68. umap/static/umap/locale/es.json +20 -12
  69. umap/static/umap/locale/eu.js +12 -4
  70. umap/static/umap/locale/eu.json +12 -4
  71. umap/static/umap/locale/fa_IR.js +12 -4
  72. umap/static/umap/locale/fa_IR.json +12 -4
  73. umap/static/umap/locale/fr.js +10 -5
  74. umap/static/umap/locale/fr.json +10 -5
  75. umap/static/umap/locale/gl.js +353 -345
  76. umap/static/umap/locale/gl.json +353 -345
  77. umap/static/umap/locale/hu.js +9 -4
  78. umap/static/umap/locale/hu.json +9 -4
  79. umap/static/umap/locale/it.js +100 -92
  80. umap/static/umap/locale/it.json +100 -92
  81. umap/static/umap/locale/ms.js +12 -4
  82. umap/static/umap/locale/ms.json +12 -4
  83. umap/static/umap/locale/nl.js +12 -4
  84. umap/static/umap/locale/nl.json +12 -4
  85. umap/static/umap/locale/pl.js +12 -4
  86. umap/static/umap/locale/pl.json +12 -4
  87. umap/static/umap/locale/pt.js +12 -4
  88. umap/static/umap/locale/pt.json +12 -4
  89. umap/static/umap/locale/pt_PT.js +12 -4
  90. umap/static/umap/locale/pt_PT.json +12 -4
  91. umap/static/umap/locale/th_TH.js +12 -4
  92. umap/static/umap/locale/th_TH.json +12 -4
  93. umap/static/umap/locale/zh_TW.js +10 -4
  94. umap/static/umap/locale/zh_TW.json +10 -4
  95. umap/static/umap/map.css +12 -8
  96. umap/static/umap/nav.css +2 -3
  97. umap/static/umap/unittests/utils.js +14 -0
  98. umap/static/umap/vars.css +2 -0
  99. umap/sync/__init__.py +0 -0
  100. umap/sync/app.py +181 -0
  101. umap/sync/payloads.py +49 -0
  102. umap/templates/auth/user_detail.html +4 -0
  103. umap/templates/auth/user_form.html +9 -6
  104. umap/templates/auth/user_stars.html +4 -0
  105. umap/templates/base.html +1 -1
  106. umap/templates/registration/login.html +2 -5
  107. umap/templates/umap/about.html +5 -0
  108. umap/templates/umap/about_summary.html +2 -2
  109. umap/templates/umap/components/provider.html +8 -0
  110. umap/templates/umap/js.html +0 -3
  111. umap/templates/umap/map_detail.html +1 -1
  112. umap/templates/umap/password_change.html +4 -0
  113. umap/templates/umap/password_change_done.html +4 -0
  114. umap/templates/umap/search.html +4 -0
  115. umap/templates/umap/team_confirm_delete.html +4 -0
  116. umap/templates/umap/team_detail.html +4 -0
  117. umap/templates/umap/team_form.html +4 -0
  118. umap/templates/umap/user_dashboard.html +1 -1
  119. umap/templates/umap/user_teams.html +4 -0
  120. umap/tests/base.py +3 -1
  121. umap/tests/integration/conftest.py +16 -23
  122. umap/tests/integration/test_basics.py +2 -2
  123. umap/tests/integration/test_caption.py +1 -0
  124. umap/tests/integration/test_draw_polygon.py +3 -3
  125. umap/tests/integration/test_edit_datalayer.py +1 -1
  126. umap/tests/integration/test_edit_map.py +3 -3
  127. umap/tests/integration/test_edit_polygon.py +1 -1
  128. umap/tests/integration/test_import.py +23 -1
  129. umap/tests/integration/test_optimistic_merge.py +1 -0
  130. umap/tests/integration/test_picto.py +8 -8
  131. umap/tests/integration/test_save.py +1 -0
  132. umap/tests/integration/test_star.py +13 -9
  133. umap/tests/integration/test_tableeditor.py +1 -0
  134. umap/tests/integration/test_websocket_sync.py +112 -33
  135. umap/tests/settings.py +2 -0
  136. umap/tests/test_datalayer.py +2 -3
  137. umap/tests/test_datalayer_views.py +20 -1
  138. umap/tests/test_empty_trash.py +10 -3
  139. umap/tests/test_map_views.py +11 -0
  140. umap/utils.py +24 -11
  141. umap/views.py +37 -6
  142. {umap_project-2.8.2.dist-info → umap_project-2.9.0b0.dist-info}/METADATA +15 -15
  143. {umap_project-2.8.2.dist-info → umap_project-2.9.0b0.dist-info}/RECORD +146 -145
  144. {umap_project-2.8.2.dist-info → umap_project-2.9.0b0.dist-info}/WHEEL +1 -1
  145. umap/management/commands/run_websocket_server.py +0 -23
  146. umap/settings/local_s3.py +0 -45
  147. umap/static/umap/bitbucket.png +0 -0
  148. umap/static/umap/github.png +0 -0
  149. umap/static/umap/js/umap.forms.js +0 -1242
  150. umap/static/umap/keycloak.png +0 -0
  151. umap/static/umap/openstreetmap.png +0 -0
  152. umap/static/umap/twitter.png +0 -0
  153. umap/static/umap/vendors/formbuilder/Leaflet.FormBuilder.js +0 -468
  154. umap/tests/test_websocket_server.py +0 -22
  155. umap/websocket_server.py +0 -202
  156. {umap_project-2.8.2.dist-info → umap_project-2.9.0b0.dist-info}/entry_points.txt +0 -0
  157. {umap_project-2.8.2.dist-info → umap_project-2.9.0b0.dist-info}/licenses/LICENSE +0 -0
@@ -1,5 +1,6 @@
1
1
  /* Uses globals for: csv2geojson, osmtogeojson (not available as ESM) */
2
2
  import { translate } from './i18n.js'
3
+ import { uMapAlert as Alert } from '../components/alerts/alert.js'
3
4
 
4
5
  export const EXPORT_FORMATS = {
5
6
  geojson: {
@@ -58,11 +59,7 @@ export class Formatter {
58
59
  }
59
60
 
60
61
  async fromGeoJSON(str) {
61
- try {
62
- return JSON.parse(str)
63
- } catch (err) {
64
- U.Alert.error(`Invalid JSON file: ${err}`)
65
- }
62
+ return JSON.parse(str)
66
63
  }
67
64
 
68
65
  async fromOSM(str) {
@@ -106,8 +103,8 @@ export class Formatter {
106
103
  message: err[0].message,
107
104
  })
108
105
  }
109
- U.Alert.error(message, 10000)
110
- console.error(err)
106
+ Alert.error(message, 10000)
107
+ console.debug(err)
111
108
  }
112
109
  if (result?.features.length) {
113
110
  callback(result)
@@ -127,7 +124,7 @@ export class Formatter {
127
124
  const doc = new DOMParser().parseFromString(x, 'text/xml')
128
125
  const errorNode = doc.querySelector('parsererror')
129
126
  if (errorNode) {
130
- U.Alert.error(translate('Cannot parse data'))
127
+ Alert.error(translate('Cannot parse data'))
131
128
  }
132
129
  return doc
133
130
  }
@@ -228,7 +228,9 @@ export default class Help {
228
228
 
229
229
  parse(container) {
230
230
  for (const element of container.querySelectorAll('[data-help]')) {
231
- this.button(element, element.dataset.help.split(','))
231
+ if (element.dataset.help) {
232
+ this.button(element, element.dataset.help.split(','))
233
+ }
232
234
  }
233
235
  }
234
236
 
@@ -305,7 +305,7 @@ export default class Importer extends Utils.WithTemplate {
305
305
  this.onSuccess()
306
306
  } catch (e) {
307
307
  this.onError(translate('Invalid umap data'))
308
- console.error(e)
308
+ console.debug(e)
309
309
  return false
310
310
  }
311
311
  }
@@ -3,6 +3,7 @@ import { translate } from './i18n.js'
3
3
  import { uMapAlert as Alert } from '../components/alerts/alert.js'
4
4
  import { ServerStored } from './saving.js'
5
5
  import * as Utils from './utils.js'
6
+ import { MutatingForm } from './form/builder.js'
6
7
 
7
8
  // Dedicated object so we can deal with a separate dirty status, and thus
8
9
  // call the endpoint only when needed, saving one call at each save.
@@ -58,7 +59,7 @@ export class MapPermissions extends ServerStored {
58
59
  selectOptions: this._umap.properties.share_statuses,
59
60
  },
60
61
  ])
61
- const builder = new U.FormBuilder(this, fields)
62
+ const builder = new MutatingForm(this, fields)
62
63
  const form = builder.build()
63
64
  container.appendChild(form)
64
65
 
@@ -133,7 +134,7 @@ export class MapPermissions extends ServerStored {
133
134
  { handler: 'ManageEditors', label: translate("Map's editors") },
134
135
  ])
135
136
 
136
- const builder = new U.FormBuilder(this, topFields)
137
+ const builder = new MutatingForm(this, topFields)
137
138
  const form = builder.build()
138
139
  container.appendChild(form)
139
140
  if (collaboratorsFields.length) {
@@ -141,7 +142,7 @@ export class MapPermissions extends ServerStored {
141
142
  `<fieldset class="separator"><legend>${translate('Manage collaborators')}</legend></fieldset>`
142
143
  )
143
144
  container.appendChild(fieldset)
144
- const builder = new U.FormBuilder(this, collaboratorsFields)
145
+ const builder = new MutatingForm(this, collaboratorsFields)
145
146
  const form = builder.build()
146
147
  container.appendChild(form)
147
148
  }
@@ -269,7 +270,7 @@ export class DataLayerPermissions extends ServerStored {
269
270
  },
270
271
  ],
271
272
  ]
272
- const builder = new U.FormBuilder(this, fields, {
273
+ const builder = new MutatingForm(this, fields, {
273
274
  className: 'umap-form datalayer-permissions',
274
275
  })
275
276
  const form = builder.build()
@@ -70,6 +70,11 @@ const BaseIcon = DivIcon.extend({
70
70
  },
71
71
 
72
72
  onAdd: () => {},
73
+
74
+ _setIconStyles: function (img, name) {
75
+ if (this.feature.isActive()) this.options.className += ' umap-icon-active'
76
+ DivIcon.prototype._setIconStyles.call(this, img, name)
77
+ },
73
78
  })
74
79
 
75
80
  const DefaultIcon = BaseIcon.extend({
@@ -86,7 +91,6 @@ const DefaultIcon = BaseIcon.extend({
86
91
  },
87
92
 
88
93
  _setIconStyles: function (img, name) {
89
- if (this.feature.isActive()) this.options.className += ' umap-icon-active'
90
94
  BaseIcon.prototype._setIconStyles.call(this, img, name)
91
95
  const color = this._getColor()
92
96
  const opacity = this._getOpacity()
@@ -88,7 +88,11 @@ const ClassifiedMixin = {
88
88
  },
89
89
 
90
90
  getColorSchemes: function (classes) {
91
- return this.colorSchemes.filter((scheme) => Boolean(colorbrewer[scheme][classes]))
91
+ const found = this.colorSchemes.filter((scheme) =>
92
+ Boolean(colorbrewer[scheme][classes])
93
+ )
94
+ if (found.length) return found
95
+ return [['', translate('Default')]]
92
96
  },
93
97
  }
94
98
 
@@ -191,7 +195,7 @@ export const Choropleth = FeatureGroup.extend({
191
195
  'options.choropleth.property',
192
196
  {
193
197
  handler: 'Select',
194
- selectOptions: this.datalayer._propertiesIndex,
198
+ selectOptions: this.datalayer.allProperties(),
195
199
  label: translate('Choropleth property value'),
196
200
  },
197
201
  ],
@@ -300,7 +304,7 @@ export const Circles = FeatureGroup.extend({
300
304
  'options.circles.property',
301
305
  {
302
306
  handler: 'Select',
303
- selectOptions: this.datalayer._propertiesIndex,
307
+ selectOptions: this.datalayer.allProperties(),
304
308
  label: translate('Property name to compute circles'),
305
309
  },
306
310
  ],
@@ -377,7 +381,7 @@ export const Categorized = FeatureGroup.extend({
377
381
 
378
382
  _getValue: function (feature) {
379
383
  const key =
380
- this.datalayer.options.categorized.property || this.datalayer._propertiesIndex[0]
384
+ this.datalayer.options.categorized.property || this.datalayer.allProperties()[0]
381
385
  return feature.properties[key]
382
386
  },
383
387
 
@@ -420,7 +424,7 @@ export const Categorized = FeatureGroup.extend({
420
424
  } else {
421
425
  this.options.colors = colorbrewer?.Accent[this._classes]
422
426
  ? colorbrewer?.Accent[this._classes]
423
- : U.COLORS // Fixme: move COLORS to modules/
427
+ : Utils.COLORS
424
428
  }
425
429
  },
426
430
 
@@ -430,7 +434,7 @@ export const Categorized = FeatureGroup.extend({
430
434
  'options.categorized.property',
431
435
  {
432
436
  handler: 'Select',
433
- selectOptions: this.datalayer._propertiesIndex,
437
+ selectOptions: this.datalayer.allProperties(),
434
438
  label: translate('Category property'),
435
439
  },
436
440
  ],
@@ -464,7 +468,7 @@ export const Categorized = FeatureGroup.extend({
464
468
 
465
469
  onEdit: function (field, builder) {
466
470
  // Only compute the categories if we're dealing with categorized
467
- if (!field.startsWith('options.categorized')) return
471
+ if (!field.startsWith('options.categorized') && field !== 'options.type') return
468
472
  // If user touches the categories, then force manual mode
469
473
  if (field === 'options.categorized.categories') {
470
474
  this.datalayer.options.categorized.mode = 'manual'
@@ -63,7 +63,17 @@ export const Cluster = L.MarkerClusterGroup.extend({
63
63
 
64
64
  addLayer: function (layer) {
65
65
  this._layers.push(layer)
66
- return L.MarkerClusterGroup.prototype.addLayer.call(this, layer)
66
+ try {
67
+ return L.MarkerClusterGroup.prototype.addLayer.call(this, layer)
68
+ } catch (error) {
69
+ console.debug(error)
70
+ // Certainly a race condition when loading a clustered layer
71
+ // while zooming (this for example can happen at load, when the
72
+ // initial zoom is changed by uMap).
73
+ // FIXME: remove when this is merged:
74
+ // https://github.com/Leaflet/Leaflet.markercluster/pull/1048/files
75
+ return this
76
+ }
67
77
  },
68
78
 
69
79
  removeLayer: function (layer) {
@@ -32,7 +32,6 @@ const ControlsMixin = {
32
32
  'locate',
33
33
  'measure',
34
34
  'editinosm',
35
- 'star',
36
35
  'tilelayers',
37
36
  ],
38
37
 
@@ -84,7 +83,6 @@ const ControlsMixin = {
84
83
  this._controls.search = new U.SearchControl()
85
84
  this._controls.embed = new Control.Embed(this._umap)
86
85
  this._controls.tilelayersChooser = new U.TileLayerChooser(this)
87
- if (this.options.user?.id) this._controls.star = new U.StarControl(this._umap)
88
86
  this._controls.editinosm = new Control.EditInOSM({
89
87
  position: 'topleft',
90
88
  widgetOptions: {
@@ -3,6 +3,7 @@ import { translate } from './i18n.js'
3
3
  import * as Utils from './utils.js'
4
4
  import { AutocompleteDatalist } from './autocomplete.js'
5
5
  import Orderable from './orderable.js'
6
+ import { MutatingForm } from './form/builder.js'
6
7
 
7
8
  const EMPTY_VALUES = ['', undefined, null]
8
9
 
@@ -129,7 +130,7 @@ class Rule {
129
130
  'options.dashArray',
130
131
  ]
131
132
  const container = DomUtil.create('div')
132
- const builder = new U.FormBuilder(this, options)
133
+ const builder = new MutatingForm(this, options)
133
134
  const defaultShapeProperties = DomUtil.add('div', '', container)
134
135
  defaultShapeProperties.appendChild(builder.build())
135
136
  const autocomplete = new AutocompleteDatalist(builder.helpers.condition.input)
@@ -447,6 +447,11 @@ export const SCHEMA = {
447
447
  label: translate('Display label'),
448
448
  inheritable: true,
449
449
  default: false,
450
+ choices: [
451
+ [true, translate('always')],
452
+ [false, translate('never')],
453
+ ['null', translate('on hover')],
454
+ ],
450
455
  },
451
456
  slideshow: {
452
457
  type: Object,
@@ -478,12 +483,6 @@ export const SCHEMA = {
478
483
  label: translate('Sort key'),
479
484
  inheritable: true,
480
485
  },
481
- starControl: {
482
- type: Boolean,
483
- impacts: ['ui'],
484
- nullable: true,
485
- label: translate('Display the star map button'),
486
- },
487
486
  stroke: {
488
487
  type: Boolean,
489
488
  impacts: ['data'],
@@ -2,6 +2,7 @@ import { DomUtil } from '../../vendors/leaflet/leaflet-src.esm.js'
2
2
  import { EXPORT_FORMATS } from './formatter.js'
3
3
  import { translate } from './i18n.js'
4
4
  import * as Utils from './utils.js'
5
+ import { MutatingForm } from './form/builder.js'
5
6
 
6
7
  export default class Share {
7
8
  constructor(umap) {
@@ -125,9 +126,8 @@ export default class Share {
125
126
  exportUrl.value = window.location.protocol + iframeExporter.buildUrl()
126
127
  }
127
128
  buildIframeCode()
128
- const builder = new U.FormBuilder(iframeExporter, UIFields, {
129
- callback: buildIframeCode,
130
- })
129
+ const builder = new MutatingForm(iframeExporter, UIFields)
130
+ builder.on('set', buildIframeCode)
131
131
  const iframeOptions = DomUtil.createFieldset(
132
132
  this.container,
133
133
  translate('Embed and link options')
@@ -61,6 +61,8 @@ export class SyncEngine {
61
61
  this._reconnectTimeout = null
62
62
  this._reconnectDelay = RECONNECT_DELAY
63
63
  this.websocketConnected = false
64
+ this.closeRequested = false
65
+ this.peerId = Utils.generateId()
64
66
  }
65
67
 
66
68
  async authenticate() {
@@ -75,10 +77,14 @@ export class SyncEngine {
75
77
  }
76
78
 
77
79
  start(authToken) {
80
+ const path = this._umap.urls.get('ws_sync', { map_id: this._umap.id })
81
+ const protocol = window.location.protocol === 'http:' ? 'ws:' : 'wss:'
78
82
  this.transport = new WebSocketTransport(
79
- this._umap.properties.websocketURI,
83
+ `${protocol}//${window.location.host}${path}`,
80
84
  authToken,
81
- this
85
+ this,
86
+ this.peerId,
87
+ this._umap.properties.user?.name
82
88
  )
83
89
  }
84
90
 
@@ -124,7 +130,7 @@ export class SyncEngine {
124
130
 
125
131
  if (this.offline) return
126
132
  if (this.transport) {
127
- this.transport.send('OperationMessage', message)
133
+ this.transport.send('OperationMessage', { sender: this.peerId, ...message })
128
134
  }
129
135
  }
130
136
 
@@ -140,9 +146,8 @@ export class SyncEngine {
140
146
  updater.applyMessage(operation)
141
147
  }
142
148
 
143
- getNumberOfConnectedPeers() {
144
- if (this.peers) return this.peers.length
145
- return 0
149
+ getPeers() {
150
+ return this.peers || {}
146
151
  }
147
152
 
148
153
  /**
@@ -176,6 +181,7 @@ export class SyncEngine {
176
181
  * @param {Object} payload
177
182
  */
178
183
  onOperationMessage(payload) {
184
+ if (payload.sender === this.peerId) return
179
185
  this._operations.storeRemoteOperations([payload])
180
186
  this._applyOperation(payload)
181
187
  }
@@ -187,9 +193,8 @@ export class SyncEngine {
187
193
  * @param {string} payload.uuid The server-assigned uuid for this peer
188
194
  * @param {string[]} payload.peers The list of peers uuids
189
195
  */
190
- onJoinResponse({ uuid, peers }) {
191
- debug('received join response', { uuid, peers })
192
- this.uuid = uuid
196
+ onJoinResponse({ peer, peers }) {
197
+ debug('received join response', { peer, peers })
193
198
  this.onListPeersResponse({ peers })
194
199
 
195
200
  // Get one peer at random
@@ -210,7 +215,7 @@ export class SyncEngine {
210
215
  * @param {string[]} payload.peers The list of peers uuids
211
216
  */
212
217
  onListPeersResponse({ peers }) {
213
- debug('received peerinfo', { peers })
218
+ debug('received peerinfo', peers)
214
219
  this.peers = peers
215
220
  this.updaters.map.update({ key: 'numberOfConnectedPeers' })
216
221
  }
@@ -285,7 +290,7 @@ export class SyncEngine {
285
290
  sendToPeer(recipient, verb, payload) {
286
291
  payload.verb = verb
287
292
  this.transport.send('PeerMessage', {
288
- sender: this.uuid,
293
+ sender: this.peerId,
289
294
  recipient: recipient,
290
295
  message: payload,
291
296
  })
@@ -297,7 +302,7 @@ export class SyncEngine {
297
302
  * @returns {string|bool} the selected peer uuid, or False if none was found.
298
303
  */
299
304
  _getRandomPeer() {
300
- const otherPeers = this.peers.filter((p) => p !== this.uuid)
305
+ const otherPeers = Object.keys(this.peers).filter((p) => p !== this.peerId)
301
306
  if (otherPeers.length > 0) {
302
307
  const random = Math.floor(Math.random() * otherPeers.length)
303
308
  return otherPeers[random]
@@ -483,7 +488,7 @@ export class Operations {
483
488
  return (
484
489
  Utils.deepEqual(local.subject, remote.subject) &&
485
490
  Utils.deepEqual(local.metadata, remote.metadata) &&
486
- (!shouldCheckKey || (shouldCheckKey && local.key == remote.key))
491
+ (!shouldCheckKey || (shouldCheckKey && local.key === remote.key))
487
492
  )
488
493
  }
489
494
  }
@@ -72,6 +72,14 @@ export class DataLayerUpdater extends BaseUpdater {
72
72
  }
73
73
  datalayer.render([key])
74
74
  }
75
+
76
+ delete({ metadata }) {
77
+ const datalayer = this.getDataLayerFromID(metadata.id)
78
+ if (datalayer) {
79
+ datalayer.del(false)
80
+ datalayer.commitDelete()
81
+ }
82
+ }
75
83
  }
76
84
 
77
85
  export class FeatureUpdater extends BaseUpdater {
@@ -3,25 +3,28 @@ const PING_INTERVAL = 30000
3
3
  const FIRST_CONNECTION_TIMEOUT = 2000
4
4
 
5
5
  export class WebSocketTransport {
6
- constructor(webSocketURI, authToken, messagesReceiver) {
6
+ constructor(webSocketURI, authToken, messagesReceiver, peerId, username) {
7
7
  this.receiver = messagesReceiver
8
- this.closeRequested = false
9
8
 
10
9
  this.websocket = new WebSocket(webSocketURI)
11
10
 
12
11
  this.websocket.onopen = () => {
13
- this.send('JoinRequest', { token: authToken })
12
+ this.send('JoinRequest', { token: authToken, peer: peerId, username })
14
13
  this.receiver.onConnection()
15
14
  }
16
15
  this.websocket.addEventListener('message', this.onMessage.bind(this))
17
16
  this.websocket.onclose = () => {
18
17
  console.log('websocket closed')
19
- if (!this.closeRequested) {
18
+ if (!this.receiver.closeRequested) {
20
19
  console.log('Not requested, reconnecting...')
21
20
  this.receiver.reconnect()
22
21
  }
23
22
  }
24
23
 
24
+ this.websocket.onerror = (error) => {
25
+ console.log('WS ERROR', error)
26
+ }
27
+
25
28
  this.ensureOpen = setInterval(() => {
26
29
  if (this.websocket.readyState !== WebSocket.OPEN) {
27
30
  this.websocket.close()
@@ -35,6 +38,7 @@ export class WebSocketTransport {
35
38
  // See https://making.close.com/posts/reliable-websockets/ for more details.
36
39
  this.pingInterval = setInterval(() => {
37
40
  if (this.websocket.readyState === WebSocket.OPEN) {
41
+ console.log('sending ping')
38
42
  this.websocket.send('ping')
39
43
  this.pongReceived = false
40
44
  setTimeout(() => {
@@ -64,7 +68,8 @@ export class WebSocketTransport {
64
68
  }
65
69
 
66
70
  close() {
67
- this.closeRequested = true
71
+ console.log('Closing')
72
+ this.receiver.closeRequested = true
68
73
  this.websocket.close()
69
74
  }
70
75
  }
@@ -2,6 +2,7 @@ import { DomEvent, DomUtil } from '../../vendors/leaflet/leaflet-src.esm.js'
2
2
  import { translate } from './i18n.js'
3
3
  import ContextMenu from './ui/contextmenu.js'
4
4
  import { WithTemplate, loadTemplate } from './utils.js'
5
+ import { MutatingForm } from './form/builder.js'
5
6
 
6
7
  const TEMPLATE = `
7
8
  <table>
@@ -103,7 +104,7 @@ export default class TableEditor extends WithTemplate {
103
104
  }
104
105
 
105
106
  resetProperties() {
106
- this.properties = this.datalayer._propertiesIndex
107
+ this.properties = this.datalayer.allProperties()
107
108
  if (this.properties.length === 0) {
108
109
  this.properties = [U.DEFAULT_LABEL_KEY, 'description']
109
110
  }
@@ -205,7 +206,7 @@ export default class TableEditor extends WithTemplate {
205
206
  const tr = event.target.closest('tr')
206
207
  const feature = this.datalayer.getFeatureById(tr.dataset.feature)
207
208
  const handler = property === 'description' ? 'Textarea' : 'Input'
208
- const builder = new U.FormBuilder(feature, [[field, { handler }]], {
209
+ const builder = new MutatingForm(feature, [[field, { handler }]], {
209
210
  id: `umap-feature-properties_${L.stamp(feature)}`,
210
211
  })
211
212
  cell.innerHTML = ''
@@ -2,13 +2,14 @@ import { DomEvent } from '../../../vendors/leaflet/leaflet-src.esm.js'
2
2
  import { translate } from '../i18n.js'
3
3
  import { WithTemplate } from '../utils.js'
4
4
  import ContextMenu from './contextmenu.js'
5
+ import * as Utils from '../utils.js'
5
6
 
6
7
  const TOP_BAR_TEMPLATE = `
7
8
  <div class="umap-main-edit-toolbox with-transition dark">
8
9
  <div class="umap-left-edit-toolbox" data-ref="left">
9
10
  <div class="logo"><a class="" href="/" title="${translate('Go to the homepage')}">uMap</a></div>
10
- <button class="map-name" type="button" data-ref="name"></button>
11
- <button class="share-status" type="button" data-ref="share"></button>
11
+ <button class="map-name flat" type="button" data-ref="name"></button>
12
+ <button class="share-status flat" type="button" data-ref="share"></button>
12
13
  </div>
13
14
  <div class="umap-right-edit-toolbox" data-ref="right">
14
15
  <button class="connected-peers round" type="button" data-ref="peers">
@@ -19,7 +20,7 @@ const TOP_BAR_TEMPLATE = `
19
20
  <i class="icon icon-16 icon-profile"></i>
20
21
  <span class="username" data-ref="username"></span>
21
22
  </button>
22
- <button class="umap-help-link" type="button" title="${translate('Help')}" data-ref="help">${translate('Help')}</button>
23
+ <button class="umap-help-link flat" type="button" title="${translate('Help')}" data-ref="help">${translate('Help')}</button>
23
24
  <button class="edit-cancel round" type="button" data-ref="cancel">
24
25
  <i class="icon icon-16 icon-restore"></i>
25
26
  <span class="">${translate('Cancel edits')}</span>
@@ -96,17 +97,22 @@ export class TopBar extends WithTemplate {
96
97
  }
97
98
  })
98
99
 
99
- const connectedPeers = this._umap.sync.getNumberOfConnectedPeers()
100
100
  this.elements.peers.addEventListener('mouseover', () => {
101
- if (!connectedPeers) return
101
+ const connectedPeers = this._umap.sync.getPeers()
102
+ if (!Object.keys(connectedPeers).length) return
103
+ const ul = Utils.loadTemplate(
104
+ `<ul>${Object.entries(connectedPeers)
105
+ .sort((el) => el !== this._umap.user?.name)
106
+ .map(([id, name]) => `<li>${name || translate('Anonymous')}</li>`)
107
+ .join('')}</ul>`
108
+ )
102
109
  this._umap.tooltip.open({
103
- content: translate('{connectedPeers} peer(s) currently connected to this map', {
104
- connectedPeers: connectedPeers,
105
- }),
110
+ content: ul,
106
111
  anchor: this.elements.peers,
107
112
  position: 'bottom',
108
113
  delay: 500,
109
114
  duration: 5000,
115
+ accent: true,
110
116
  })
111
117
  })
112
118
 
@@ -145,7 +151,9 @@ export class TopBar extends WithTemplate {
145
151
  }
146
152
 
147
153
  redraw() {
148
- this.elements.peers.hidden = !this._umap.getProperty('syncEnabled')
154
+ const syncEnabled = this._umap.getProperty('syncEnabled')
155
+ this.elements.peers.hidden = !syncEnabled
156
+ this.elements.cancel.hidden = syncEnabled
149
157
  this.elements.saveLabel.hidden = this._umap.permissions.isDraft()
150
158
  this.elements.saveDraftLabel.hidden = !this._umap.permissions.isDraft()
151
159
  }
@@ -2,27 +2,18 @@ export class Positioned {
2
2
  openAt({ anchor, position }) {
3
3
  if (anchor && position === 'top') {
4
4
  this.anchorTop(anchor)
5
- } else if (anchor && position === 'left') {
6
- this.anchorLeft(anchor)
7
5
  } else if (anchor && position === 'bottom') {
8
6
  this.anchorBottom(anchor)
9
- } else {
10
- this.anchorAbsolute()
11
7
  }
12
8
  }
13
9
 
14
- anchorAbsolute() {
15
- this.container.className = ''
16
- const left =
17
- this.parent.offsetLeft +
18
- this.parent.clientWidth / 2 -
19
- this.container.clientWidth / 2
20
- const top = this.parent.offsetTop + 75
21
- this.setPosition({ top: top, left: left })
10
+ toggleClassPosition(position) {
11
+ this.container.classList.toggle('tooltip-bottom', position === 'bottom')
12
+ this.container.classList.toggle('tooltip-top', position === 'top')
22
13
  }
23
14
 
24
15
  anchorTop(el) {
25
- this.container.className = 'tooltip-top'
16
+ this.toggleClassPosition('top')
26
17
  const coords = this.getPosition(el)
27
18
  this.setPosition({
28
19
  left: coords.left - 10,
@@ -31,23 +22,15 @@ export class Positioned {
31
22
  }
32
23
 
33
24
  anchorBottom(el) {
34
- this.container.className = 'tooltip-bottom'
25
+ this.toggleClassPosition('bottom')
35
26
  const coords = this.getPosition(el)
27
+ const selfCoords = this.getPosition(this.container)
36
28
  this.setPosition({
37
- left: coords.left,
29
+ left: coords.left + coords.width / 2 - selfCoords.width / 2,
38
30
  top: coords.bottom + 11,
39
31
  })
40
32
  }
41
33
 
42
- anchorLeft(el) {
43
- this.container.className = 'tooltip-left'
44
- const coords = this.getPosition(el)
45
- this.setPosition({
46
- top: coords.top,
47
- right: document.documentElement.offsetWidth - coords.left + 11,
48
- })
49
- }
50
-
51
34
  getPosition(el) {
52
35
  return el.getBoundingClientRect()
53
36
  }
@@ -1,24 +1,32 @@
1
- import { DomEvent, DomUtil } from '../../../vendors/leaflet/leaflet-src.esm.js'
1
+ import { DomEvent } from '../../../vendors/leaflet/leaflet-src.esm.js'
2
2
  import { translate } from '../i18n.js'
3
3
  import { Positioned } from './base.js'
4
+ import * as Utils from '../utils.js'
4
5
 
5
6
  export default class Tooltip extends Positioned {
6
7
  constructor(parent) {
7
8
  super()
8
9
  this.parent = parent
9
- this.container = DomUtil.create('div', 'with-transition', this.parent)
10
- this.container.id = 'umap-tooltip-container'
10
+ this.container = Utils.loadTemplate('<div class="umap-tooltip-container"></div>')
11
+ this.parent.appendChild(this.container)
11
12
  DomEvent.disableClickPropagation(this.container)
12
- DomEvent.on(this.container, 'contextmenu', DomEvent.stopPropagation) // Do not activate our custom context menu.
13
- DomEvent.on(this.container, 'wheel', DomEvent.stopPropagation)
14
- DomEvent.on(this.container, 'MozMousePixelScroll', DomEvent.stopPropagation)
13
+ this.container.addEventListener('contextmenu', (event) => event.stopPropagation()) // Do not activate our custom context menu.
14
+ this.container.addEventListener('wheel', (event) => event.stopPropagation())
15
+ this.container.addEventListener('MozMousePixelScroll', (event) =>
16
+ event.stopPropagation()
17
+ )
15
18
  }
16
19
 
17
20
  open(opts) {
21
+ this.container.classList.toggle('tooltip-accent', Boolean(opts.accent))
18
22
  const showIt = () => {
23
+ if (opts.content.nodeType === 1) {
24
+ this.container.appendChild(opts.content)
25
+ } else {
26
+ this.container.innerHTML = Utils.escapeHTML(opts.content)
27
+ }
28
+ this.parent.classList.add('umap-tooltip')
19
29
  this.openAt(opts)
20
- L.DomUtil.addClass(this.parent, 'umap-tooltip')
21
- this.container.innerHTML = U.Utils.escapeHTML(opts.content)
22
30
  }
23
31
  this.TOOLTIP_ID = window.setTimeout(L.bind(showIt, this), opts.delay || 0)
24
32
  const id = this.TOOLTIP_ID
@@ -26,7 +34,7 @@ export default class Tooltip extends Positioned {
26
34
  this.close(id)
27
35
  }
28
36
  if (opts.anchor) {
29
- L.DomEvent.once(opts.anchor, 'mouseout', closeIt)
37
+ opts.anchor.addEventListener('mouseout', closeIt, { once: true })
30
38
  }
31
39
  if (opts.duration !== Number.POSITIVE_INFINITY) {
32
40
  window.setTimeout(closeIt, opts.duration || 3000)
@@ -38,9 +46,9 @@ export default class Tooltip extends Positioned {
38
46
  // in the meantime. Eg. after a mouseout from the anchor.
39
47
  window.clearTimeout(id)
40
48
  if (id && id !== this.TOOLTIP_ID) return
41
- this.container.className = ''
49
+ this.toggleClassPosition()
42
50
  this.container.innerHTML = ''
43
51
  this.setPosition({})
44
- L.DomUtil.removeClass(this.parent, 'umap-tooltip')
52
+ this.parent.classList.remove('umap-tooltip')
45
53
  }
46
54
  }