umap-project 3.4.2__py3-none-any.whl → 3.6.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.
Files changed (170) hide show
  1. umap/__init__.py +1 -1
  2. umap/locale/br/LC_MESSAGES/django.mo +0 -0
  3. umap/locale/br/LC_MESSAGES/django.po +71 -57
  4. umap/locale/de/LC_MESSAGES/django.mo +0 -0
  5. umap/locale/de/LC_MESSAGES/django.po +20 -16
  6. umap/locale/en/LC_MESSAGES/django.po +14 -14
  7. umap/locale/hu/LC_MESSAGES/django.mo +0 -0
  8. umap/locale/hu/LC_MESSAGES/django.po +20 -16
  9. umap/locale/pl/LC_MESSAGES/django.mo +0 -0
  10. umap/locale/pl/LC_MESSAGES/django.po +32 -27
  11. umap/locale/zh_TW/LC_MESSAGES/django.mo +0 -0
  12. umap/locale/zh_TW/LC_MESSAGES/django.po +20 -16
  13. umap/management/commands/clean_tilelayer.py +0 -1
  14. umap/management/commands/search_maps.py +95 -0
  15. umap/settings/__init__.py +9 -1
  16. umap/settings/base.py +7 -6
  17. umap/static/umap/css/icon.css +8 -0
  18. umap/static/umap/img/16-white.svg +5 -2
  19. umap/static/umap/img/16.svg +1 -1
  20. umap/static/umap/img/source/16-white.svg +7 -4
  21. umap/static/umap/img/source/16.svg +1 -1
  22. umap/static/umap/js/modules/autocomplete.js +1 -9
  23. umap/static/umap/js/modules/browser.js +27 -10
  24. umap/static/umap/js/modules/data/features.js +3 -2
  25. umap/static/umap/js/modules/data/fields.js +12 -2
  26. umap/static/umap/js/modules/data/layer.js +13 -9
  27. umap/static/umap/js/modules/domutils.js +4 -0
  28. umap/static/umap/js/modules/filters.js +11 -10
  29. umap/static/umap/js/modules/form/builder.js +17 -16
  30. umap/static/umap/js/modules/form/fields.js +16 -16
  31. umap/static/umap/js/modules/permissions.js +10 -2
  32. umap/static/umap/js/modules/rendering/controls.js +202 -9
  33. umap/static/umap/js/modules/rendering/layers/classified.js +1 -1
  34. umap/static/umap/js/modules/rendering/map.js +45 -35
  35. umap/static/umap/js/modules/rendering/template.js +12 -6
  36. umap/static/umap/js/modules/rules.js +1 -1
  37. umap/static/umap/js/modules/ui/bar.js +2 -1
  38. umap/static/umap/js/modules/ui/hash.js +36 -0
  39. umap/static/umap/js/modules/ui/loader.js +26 -0
  40. umap/static/umap/js/modules/ui/panel.js +7 -0
  41. umap/static/umap/js/modules/umap.js +6 -0
  42. umap/static/umap/js/modules/utils.js +5 -4
  43. umap/static/umap/js/umap.controls.js +0 -182
  44. umap/static/umap/locale/am_ET.js +2 -5
  45. umap/static/umap/locale/am_ET.json +2 -5
  46. umap/static/umap/locale/ar.js +2 -5
  47. umap/static/umap/locale/ar.json +2 -5
  48. umap/static/umap/locale/ast.js +2 -5
  49. umap/static/umap/locale/ast.json +2 -5
  50. umap/static/umap/locale/bg.js +2 -5
  51. umap/static/umap/locale/bg.json +2 -5
  52. umap/static/umap/locale/br.js +40 -43
  53. umap/static/umap/locale/br.json +40 -43
  54. umap/static/umap/locale/ca.js +2 -5
  55. umap/static/umap/locale/ca.json +2 -5
  56. umap/static/umap/locale/cs_CZ.js +0 -3
  57. umap/static/umap/locale/cs_CZ.json +0 -3
  58. umap/static/umap/locale/da.js +1 -4
  59. umap/static/umap/locale/da.json +1 -4
  60. umap/static/umap/locale/de.js +27 -30
  61. umap/static/umap/locale/de.json +27 -30
  62. umap/static/umap/locale/el.js +0 -3
  63. umap/static/umap/locale/el.json +0 -3
  64. umap/static/umap/locale/en.js +0 -3
  65. umap/static/umap/locale/en.json +0 -3
  66. umap/static/umap/locale/en_US.json +2 -5
  67. umap/static/umap/locale/es.js +0 -3
  68. umap/static/umap/locale/es.json +0 -3
  69. umap/static/umap/locale/et.js +0 -3
  70. umap/static/umap/locale/et.json +0 -3
  71. umap/static/umap/locale/eu.js +0 -3
  72. umap/static/umap/locale/eu.json +0 -3
  73. umap/static/umap/locale/fa_IR.js +0 -3
  74. umap/static/umap/locale/fa_IR.json +0 -3
  75. umap/static/umap/locale/fi.js +2 -5
  76. umap/static/umap/locale/fi.json +2 -5
  77. umap/static/umap/locale/fr.js +3 -6
  78. umap/static/umap/locale/fr.json +3 -6
  79. umap/static/umap/locale/gl.js +0 -3
  80. umap/static/umap/locale/gl.json +0 -3
  81. umap/static/umap/locale/he.js +2 -5
  82. umap/static/umap/locale/he.json +2 -5
  83. umap/static/umap/locale/hr.js +2 -5
  84. umap/static/umap/locale/hr.json +2 -5
  85. umap/static/umap/locale/hu.js +7 -10
  86. umap/static/umap/locale/hu.json +7 -10
  87. umap/static/umap/locale/id.js +2 -5
  88. umap/static/umap/locale/id.json +2 -5
  89. umap/static/umap/locale/is.js +0 -3
  90. umap/static/umap/locale/is.json +0 -3
  91. umap/static/umap/locale/it.js +0 -3
  92. umap/static/umap/locale/it.json +0 -3
  93. umap/static/umap/locale/ja.js +2 -5
  94. umap/static/umap/locale/ja.json +2 -5
  95. umap/static/umap/locale/ko.js +2 -5
  96. umap/static/umap/locale/ko.json +2 -5
  97. umap/static/umap/locale/lt.js +2 -5
  98. umap/static/umap/locale/lt.json +2 -5
  99. umap/static/umap/locale/ms.js +0 -3
  100. umap/static/umap/locale/ms.json +0 -3
  101. umap/static/umap/locale/nl.js +0 -3
  102. umap/static/umap/locale/nl.json +0 -3
  103. umap/static/umap/locale/no.js +2 -5
  104. umap/static/umap/locale/no.json +2 -5
  105. umap/static/umap/locale/pl.js +2 -5
  106. umap/static/umap/locale/pl.json +2 -5
  107. umap/static/umap/locale/pl_PL.json +2 -5
  108. umap/static/umap/locale/pt.js +0 -3
  109. umap/static/umap/locale/pt.json +0 -3
  110. umap/static/umap/locale/pt_BR.js +2 -5
  111. umap/static/umap/locale/pt_BR.json +2 -5
  112. umap/static/umap/locale/pt_PT.js +2 -5
  113. umap/static/umap/locale/pt_PT.json +2 -5
  114. umap/static/umap/locale/ro.js +2 -5
  115. umap/static/umap/locale/ro.json +2 -5
  116. umap/static/umap/locale/ru.js +2 -5
  117. umap/static/umap/locale/ru.json +2 -5
  118. umap/static/umap/locale/sk_SK.js +2 -5
  119. umap/static/umap/locale/sk_SK.json +2 -5
  120. umap/static/umap/locale/sl.js +2 -5
  121. umap/static/umap/locale/sl.json +2 -5
  122. umap/static/umap/locale/sr.js +2 -5
  123. umap/static/umap/locale/sr.json +2 -5
  124. umap/static/umap/locale/sv.js +2 -5
  125. umap/static/umap/locale/sv.json +2 -5
  126. umap/static/umap/locale/th_TH.js +2 -5
  127. umap/static/umap/locale/th_TH.json +2 -5
  128. umap/static/umap/locale/tr.js +2 -5
  129. umap/static/umap/locale/tr.json +2 -5
  130. umap/static/umap/locale/uk_UA.js +2 -5
  131. umap/static/umap/locale/uk_UA.json +2 -5
  132. umap/static/umap/locale/vi.js +2 -5
  133. umap/static/umap/locale/vi.json +2 -5
  134. umap/static/umap/locale/vi_VN.json +2 -5
  135. umap/static/umap/locale/zh.js +2 -5
  136. umap/static/umap/locale/zh.json +2 -5
  137. umap/static/umap/locale/zh_CN.json +2 -5
  138. umap/static/umap/locale/zh_TW.Big5.json +2 -5
  139. umap/static/umap/locale/zh_TW.js +1 -4
  140. umap/static/umap/locale/zh_TW.json +1 -4
  141. umap/static/umap/map.css +1 -17
  142. umap/static/umap/vendors/locatecontrol/L.Control.Locate.esm.js +942 -0
  143. umap/static/umap/vendors/photon/leaflet.photon.esm.js +472 -0
  144. umap/sync/app.py +4 -1
  145. umap/templates/umap/css.html +0 -2
  146. umap/templates/umap/js.html +0 -5
  147. umap/templates/umap/team_form.html +2 -1
  148. umap/tests/fixtures/test_upload_data_with_enum.umap +151 -0
  149. umap/tests/integration/test_edit_map.py +2 -0
  150. umap/tests/integration/test_filters.py +24 -0
  151. umap/tests/integration/test_import.py +40 -23
  152. umap/tests/integration/test_map.py +1 -1
  153. umap/tests/integration/test_optimistic_merge.py +7 -1
  154. umap/tests/integration/test_remote_data.py +1 -1
  155. umap/tests/test_search_maps_command.py +44 -0
  156. umap/utils.py +9 -3
  157. umap/views.py +17 -4
  158. {umap_project-3.4.2.dist-info → umap_project-3.6.1.dist-info}/METADATA +24 -18
  159. {umap_project-3.4.2.dist-info → umap_project-3.6.1.dist-info}/RECORD +162 -163
  160. {umap_project-3.4.2.dist-info → umap_project-3.6.1.dist-info}/WHEEL +1 -1
  161. umap/static/umap/vendors/hash/leaflet-hash.js +0 -162
  162. umap/static/umap/vendors/loading/Control.Loading.css +0 -26
  163. umap/static/umap/vendors/loading/Control.Loading.js +0 -351
  164. umap/static/umap/vendors/locatecontrol/L.Control.Locate.min.css +0 -1
  165. umap/static/umap/vendors/locatecontrol/L.Control.Locate.min.css.map +0 -1
  166. umap/static/umap/vendors/locatecontrol/L.Control.Locate.min.js +0 -4
  167. umap/static/umap/vendors/locatecontrol/L.Control.Locate.min.js.map +0 -1
  168. umap/static/umap/vendors/photon/leaflet.photon.js +0 -487
  169. {umap_project-3.4.2.dist-info → umap_project-3.6.1.dist-info}/entry_points.txt +0 -0
  170. {umap_project-3.4.2.dist-info → umap_project-3.6.1.dist-info}/licenses/LICENSE +0 -0
@@ -1,6 +1,17 @@
1
- import { Control, LayerGroup } from '../../../vendors/leaflet/leaflet-src.esm.js'
1
+ import {
2
+ Control,
3
+ LayerGroup,
4
+ latLng,
5
+ Icon,
6
+ Marker,
7
+ } from '../../../vendors/leaflet/leaflet-src.esm.js'
8
+ import {
9
+ PhotonSearch,
10
+ PhotonReverse,
11
+ } from '../../../vendors/photon/leaflet.photon.esm.js'
2
12
  import * as Utils from '../utils.js'
3
13
  import { translate } from '../i18n.js'
14
+ import { uMapAlert as Alert } from '../../components/alerts/alert.js'
4
15
 
5
16
  export const HomeControl = Control.extend({
6
17
  options: {
@@ -9,8 +20,9 @@ export const HomeControl = Control.extend({
9
20
 
10
21
  onAdd: (map) => {
11
22
  const path = map._umap.getStaticPathFor('home.svg')
23
+ const homeURL = map._umap.urls.get('home')
12
24
  const container = Utils.loadTemplate(
13
- `<a href="/" class="home-button" title="${translate('Back to home')}"><img src="${path}" alt="${translate('Home logo')}" width="38px" height="38px" /></a>`
25
+ `<a href="${homeURL}" class="home-button" title="${translate('Back to home')}"><img src="${path}" alt="${translate('Home logo')}" width="38px" height="38px" /></a>`
14
26
  )
15
27
  return container
16
28
  },
@@ -124,6 +136,7 @@ const BaseButton = Control.extend({
124
136
  initialize: function (umap, options) {
125
137
  this._umap = umap
126
138
  Control.prototype.initialize.call(this, options)
139
+ this.afterInit()
127
140
  },
128
141
 
129
142
  onAdd: function (map) {
@@ -153,6 +166,7 @@ const BaseButton = Control.extend({
153
166
  this.afterRemove(map)
154
167
  },
155
168
 
169
+ afterInit: () => {},
156
170
  afterAdd: (container, map) => {},
157
171
  afterRemove: (map) => {},
158
172
  })
@@ -244,7 +258,7 @@ export const SearchControl = BaseButton.extend({
244
258
  const [container, { input, resultsContainer }] =
245
259
  Utils.loadTemplateWithRefs(template)
246
260
  const id = Math.random()
247
- this.search = new U.Search(
261
+ this.search = new Search(
248
262
  this._umap._leafletMap,
249
263
  input,
250
264
  this.layer,
@@ -252,10 +266,10 @@ export const SearchControl = BaseButton.extend({
252
266
  )
253
267
  this._umap.panel.open({ content: container }).then(() => {
254
268
  this.search.on('ajax:send', () => {
255
- this._umap.fire('dataloading', { id: id })
269
+ this._umap.loader.start(id)
256
270
  })
257
271
  this.search.on('ajax:return', () => {
258
- this._umap.fire('dataload', { id: id })
272
+ this._umap.loader.stop(id)
259
273
  })
260
274
  this.search.resultsContainer = resultsContainer
261
275
  input.focus()
@@ -282,19 +296,16 @@ export const AttributionControl = Control.Attribution.extend({
282
296
  ${originalCredits}
283
297
  <span data-ref="short"> — ${Utils.toHTML(shortCredit)}</span>
284
298
  <a href="#" data-ref="caption"> — ${translate('Open caption')}</a>
285
- <a href="/" data-ref="home"> — ${translate('Home')}</a>
286
299
  <a href="https://umap-project.org/" data-ref="site"> — ${translate('Powered by uMap')}</a>
287
300
  <a href="#" class="attribution-toggle"></a>
288
301
  </div>
289
302
  `
290
- const [container, { short, caption, home, site }] =
291
- Utils.loadTemplateWithRefs(template)
303
+ const [container, { short, caption, site }] = Utils.loadTemplateWithRefs(template)
292
304
  caption.addEventListener('click', () => this._map._umap.openCaption())
293
305
  this._container.appendChild(container)
294
306
  short.hidden = !shortCredit
295
307
  caption.hidden = !captionMenus
296
308
  site.hidden = !captionMenus
297
- home.hidden = this._map._umap.isEmbed || !captionMenus
298
309
  },
299
310
  })
300
311
 
@@ -359,3 +370,185 @@ export const TileLayerChooser = BaseButton.extend({
359
370
  return li
360
371
  },
361
372
  })
373
+
374
+ export const LocateControl = BaseButton.extend({
375
+ options: {
376
+ position: 'topleft',
377
+ title: translate('Center map on your location'),
378
+ icon: 'icon-locate',
379
+ },
380
+
381
+ async start() {
382
+ await this.loadPlugin()
383
+ this._locate.start()
384
+ },
385
+
386
+ stop() {
387
+ this._locate?.stop()
388
+ },
389
+
390
+ async loadPlugin() {
391
+ if (this._locate) return
392
+ const { LocateControl } = await import(
393
+ '../../../vendors/locatecontrol/L.Control.Locate.esm.js'
394
+ )
395
+ this._locate = new LocateControl({
396
+ strings: {
397
+ title: translate('Center map on your location'),
398
+ },
399
+ showPopup: false,
400
+ flyTo: this.options.easing,
401
+ onLocationError: (err) => Alert.error(err.message),
402
+ })
403
+ this._locate._map = this._umap._leafletMap
404
+ this._locate.onAdd(this._umap._leafletMap)
405
+ },
406
+
407
+ async onClick() {
408
+ if (this._locate?._active) {
409
+ this.stop()
410
+ } else {
411
+ this.start()
412
+ }
413
+ },
414
+
415
+ afterRemove() {
416
+ this.stop()
417
+ },
418
+
419
+ afterInit() {
420
+ this._umap._leafletMap.on('locateactivate', () => {
421
+ this._container.classList.add('active')
422
+ })
423
+ this._umap._leafletMap.on('locatedeactivate', () => {
424
+ this._container.classList.remove('active')
425
+ })
426
+ },
427
+ })
428
+
429
+ export const Search = PhotonSearch.extend({
430
+ initialize: function (map, input, layer, options) {
431
+ this.options.placeholder = translate('Type a place name or coordinates')
432
+ this.options.location_bias_scale = 0.5
433
+ PhotonSearch.prototype.initialize.call(this, map, input, options)
434
+ this.options.url = map.options.urls.search
435
+ if (map.options.maxBounds) this.options.bbox = map.options.maxBounds.toBBoxString()
436
+ this.reverse = new PhotonReverse({
437
+ handleResults: (geojson) => {
438
+ this.handleResultsWithReverse(geojson)
439
+ },
440
+ })
441
+ this.layer = layer
442
+ },
443
+
444
+ handleResultsWithReverse: function (geojson) {
445
+ const latlng = this.reverse.latlng
446
+ geojson.features.unshift({
447
+ type: 'Feature',
448
+ geometry: { type: 'Point', coordinates: [latlng.lng, latlng.lat] },
449
+ properties: {
450
+ name: translate('Go to "{coords}"', { coords: `${latlng.lat} ${latlng.lng}` }),
451
+ },
452
+ })
453
+
454
+ this.handleResults(geojson)
455
+ },
456
+
457
+ search: function () {
458
+ this.layer.clearLayers()
459
+ const pattern = /^(?<lat>[-+]?\d{1,2}[.,]\d+)\s*[ ,]\s*(?<lng>[-+]?\d{1,3}[.,]\d+)$/
460
+ if (pattern.test(this.input.value)) {
461
+ this.hide()
462
+ const { lat, lng } = pattern.exec(this.input.value).groups
463
+ const latlng = latLng(lat, lng)
464
+ if (Utils.LatLngIsValid(latlng)) {
465
+ this.reverse.doReverse(latlng)
466
+ } else {
467
+ Alert.error(translate('Invalid latitude or longitude'))
468
+ }
469
+ return
470
+ }
471
+ // Only numbers, abort.
472
+ if (/^[\d .,]*$/.test(this.input.value)) return
473
+ // Do normal search
474
+ this.options.includePosition = this.map.getZoom() > 10
475
+ PhotonSearch.prototype.search.call(this)
476
+ },
477
+
478
+ onBlur: function (e) {
479
+ // Overrided because we don't want to hide the results on blur.
480
+ this.fire('blur')
481
+ },
482
+
483
+ formatResult: function (feature, el) {
484
+ const [tools, { point, geom }] = Utils.loadTemplateWithRefs(`
485
+ <span class="search-result-tools">
486
+ <button type="button" title="${translate('Add this geometry to my map')}" data-ref=geom><i class="icon icon-16 icon-polygon-plus"></i></button>
487
+ <button type="button" title="${translate('Add this place to my map')}" data-ref=point><i class="icon icon-16 icon-marker-plus"></i></button>
488
+ </span>
489
+ `)
490
+ geom.hidden = !['R', 'W'].includes(feature.properties.osm_type)
491
+ point.addEventListener('mousedown', (event) => {
492
+ event.stopPropagation()
493
+ const datalayer = this.map._umap.defaultEditDataLayer()
494
+ const marker = datalayer.makeFeature(feature)
495
+ marker.edit()
496
+ })
497
+ geom.addEventListener('mousedown', async (event) => {
498
+ event.stopPropagation()
499
+ const osm_id = feature.properties.osm_id
500
+ const types = {
501
+ R: 'relation',
502
+ W: 'way',
503
+ N: 'node',
504
+ }
505
+ const osm_type = types[feature.properties.osm_type]
506
+ if (!osm_type || !osm_id) return
507
+ const importer = this.map._umap.importer
508
+ importer.build()
509
+ importer.format = 'geojson'
510
+ importer.raw = await this.getOSMObject(osm_type, osm_id)
511
+ importer.submit()
512
+ })
513
+ el.appendChild(tools)
514
+ this._formatResult(feature, el)
515
+ const path = this.map._umap.getStaticPathFor('target.svg')
516
+ const icon = new Icon({
517
+ iconUrl: path,
518
+ iconSize: [24, 24],
519
+ iconAnchor: [12, 12],
520
+ })
521
+ const coords = feature.geometry.coordinates
522
+ const target = new Marker([coords[1], coords[0]], { icon })
523
+ el.addEventListener('mouseover', (event) => {
524
+ target.addTo(this.layer)
525
+ })
526
+ el.addEventListener('mouseout', (event) => {
527
+ target.removeFrom(this.layer)
528
+ })
529
+ },
530
+
531
+ async getOSMObject(osm_type, osm_id) {
532
+ const url = `https://www.openstreetmap.org/api/0.6/${osm_type}/${osm_id}/full`
533
+ const response = await this.map._umap.request.get(url)
534
+ if (response?.ok) {
535
+ const data = await this.map._umap.formatter.fromOSM(await response.text())
536
+ data.features = data.features.filter(
537
+ (feature) => feature.properties.id === `${osm_type}/${osm_id}`
538
+ )
539
+ return JSON.stringify(data)
540
+ }
541
+ },
542
+
543
+ setChoice: function (choice) {
544
+ choice = choice || this.RESULTS[this.CURRENT]
545
+ if (choice) {
546
+ const feature = choice.feature
547
+ const zoom = Math.max(this.map.getZoom(), 14) // Never unzoom.
548
+ this.map.setView(
549
+ [feature.geometry.coordinates[1], feature.geometry.coordinates[0]],
550
+ zoom
551
+ )
552
+ }
553
+ },
554
+ })
@@ -80,7 +80,7 @@ const ClassifiedMixin = {
80
80
  const ul = Utils.loadTemplate('<ul></ul>')
81
81
  const items = this.getLegendItems()
82
82
  for (const [color, label] of items) {
83
- const rgbColor = DOMUtils.hexToRGB(color)
83
+ const rgbColor = DOMUtils.colorToRGB(color)
84
84
  const opacity = this.datalayer.getOption('fillOpacity')
85
85
  const bgColor = `rgba(${rgbColor.join(',')}, ${opacity})`
86
86
  const li = Utils.loadTemplate(`
@@ -7,6 +7,7 @@ import {
7
7
  latLng,
8
8
  LatLng,
9
9
  LatLngBounds,
10
+ stamp,
10
11
  setOptions,
11
12
  TileLayer,
12
13
  } from '../../../vendors/leaflet/leaflet-src.esm.js'
@@ -20,6 +21,7 @@ import {
20
21
  EmbedControl,
21
22
  EditControl,
22
23
  HomeControl,
24
+ LocateControl,
23
25
  MoreControl,
24
26
  PermanentCreditsControl,
25
27
  TileLayerChooser,
@@ -69,19 +71,7 @@ const ControlsMixin = {
69
71
  })
70
72
  this._controls.datalayers = new DataLayersControl(this._umap)
71
73
  this._controls.caption = new CaptionControl(this._umap)
72
- this._controls.locate = new U.Locate(this, {
73
- strings: {
74
- title: translate('Center map on your location'),
75
- },
76
- showPopup: false,
77
- // We style this control in our own CSS for consistency with other controls,
78
- // but the control breaks if we don't specify a class here, so a fake class
79
- // will do.
80
- icon: 'umap-fake-class',
81
- iconLoading: 'umap-fake-class',
82
- flyTo: this.options.easing,
83
- onLocationError: (err) => U.Alert.error(err.message),
84
- })
74
+ this._controls.locate = new LocateControl(this._umap)
85
75
  this._controls.fullscreen = new Control.Fullscreen({
86
76
  title: {
87
77
  false: translate('View Fullscreen'),
@@ -174,7 +164,19 @@ const ManageTilelayerMixin = {
174
164
  if (tilelayer === this.selectedTilelayer) {
175
165
  return
176
166
  }
167
+ const onLoading = () => {
168
+ this._umap.loader.start(stamp(tilelayer))
169
+ }
170
+ const onLoad = () => {
171
+ this._umap.loader.stop(stamp(tilelayer))
172
+ }
177
173
  try {
174
+ tilelayer.on('loading', onLoading)
175
+ tilelayer.on('load', onLoad)
176
+ tilelayer.on('remove', () => {
177
+ tilelayer.off('loading', onLoading)
178
+ tilelayer.off('load', onLoad)
179
+ })
178
180
  this.addLayer(tilelayer)
179
181
  this.fire('baselayerchange', { layer: tilelayer })
180
182
  if (this.selectedTilelayer) {
@@ -253,19 +255,13 @@ export const LeafletMap = BaseMap.extend({
253
255
 
254
256
  BaseMap.prototype.initialize.call(this, element, options)
255
257
 
256
- // After calling parent initialize, as we are doing initCenter our-selves
257
-
258
- this.loader = new Control.Loading()
259
- this.loader.onAdd(this)
260
-
261
- if (!this.options.noControl) {
262
- DomEvent.on(document.body, 'dataloading', (event) =>
263
- this.fire('dataloading', event.detail)
264
- )
265
- DomEvent.on(document.body, 'dataload', (event) =>
266
- this.fire('dataload', event.detail)
267
- )
268
- }
258
+ document.body.addEventListener('mapview:update', (event) => {
259
+ let { zoom, latlng } = event.detail
260
+ if (!Utils.LatLngIsValid(latlng)) return
261
+ zoom = Math.min(zoom, this.getMaxZoom())
262
+ zoom = Math.max(zoom, this.getMinZoom())
263
+ this.setView(latlng, zoom)
264
+ })
269
265
 
270
266
  this.on('baselayerchange', (e) => {
271
267
  if (this._controls.miniMap) this._controls.miniMap.onMainMapBaseLayerChange(e)
@@ -276,6 +272,21 @@ export const LeafletMap = BaseMap.extend({
276
272
  this.initControls()
277
273
  // Needs locate control and hash to exist
278
274
  this.initCenter()
275
+
276
+ // Wait for URL to have been parsed before modifying the hash
277
+ const updateHash = () => {
278
+ const center = this.getCenter()
279
+ document.body.dispatchEvent(
280
+ new CustomEvent('mapview:updated', {
281
+ detail: {
282
+ zoom: this.getZoom(),
283
+ latlng: [center.lat.toFixed(6), center.lng.toFixed(6)],
284
+ },
285
+ })
286
+ )
287
+ }
288
+ this.on('moveend', updateHash)
289
+ updateHash()
279
290
  this.initTileLayers()
280
291
  this.renderUI()
281
292
  },
@@ -316,14 +327,13 @@ export const LeafletMap = BaseMap.extend({
316
327
  this.setView(this.options.center, this.options.zoom)
317
328
  },
318
329
 
319
- initCenter: function () {
330
+ initCenter: async function () {
320
331
  this._setDefaultCenter()
321
- if (this.options.hash) this.addHash()
322
- if (this.options.hash && this._hash.parseHash(location.hash)) {
332
+ if (this.options.hash && window.location.hash) {
323
333
  // FIXME An invalid hash will cause the load to fail
324
- this._hash.update()
334
+ this._umap.hash.parse()
325
335
  } else if (this.options.defaultView === 'locate' && !this.options.noControl) {
326
- this._controls.locate.start()
336
+ await this._controls.locate.start()
327
337
  } else if (this.options.defaultView === 'data') {
328
338
  this._umap.onceDataLoaded(this._umap.fitDataBounds)
329
339
  } else if (this.options.defaultView === 'latest') {
@@ -343,10 +353,10 @@ export const LeafletMap = BaseMap.extend({
343
353
  },
344
354
 
345
355
  handleLimitBounds: function () {
346
- const south = Number.parseFloat(this.options.limitBounds.south)
347
- const west = Number.parseFloat(this.options.limitBounds.west)
348
- const north = Number.parseFloat(this.options.limitBounds.north)
349
- const east = Number.parseFloat(this.options.limitBounds.east)
356
+ const south = Number.parseFloat(this.options.limitBounds?.south)
357
+ const west = Number.parseFloat(this.options.limitBounds?.west)
358
+ const north = Number.parseFloat(this.options.limitBounds?.north)
359
+ const east = Number.parseFloat(this.options.limitBounds?.east)
350
360
  if (
351
361
  !Number.isNaN(south) &&
352
362
  !Number.isNaN(west) &&
@@ -34,8 +34,7 @@ export default async function loadTemplate(name, feature, container) {
34
34
  class PopupTemplate {
35
35
  renderTitle(feature) {}
36
36
 
37
- renderBody(feature) {
38
- const template = feature.getOption('popupContentTemplate')
37
+ toHTML(feature, template) {
39
38
  const target = feature.getOption('outlinkTarget')
40
39
  const properties = feature.extendedProperties()
41
40
  // Resolve properties inside description
@@ -44,9 +43,15 @@ class PopupTemplate {
44
43
  properties
45
44
  )
46
45
  properties.name = properties.name ?? feature.getDisplayName()
47
- let content = Utils.greedyTemplate(template, properties)
48
- content = Utils.toHTML(content, { target: target })
49
- return Utils.loadTemplate(`<div class="umap-popup-container text">${content}</div>`)
46
+ const content = Utils.greedyTemplate(template, properties)
47
+ return Utils.toHTML(content, { target })
48
+ }
49
+
50
+ renderBody(feature) {
51
+ const template = feature.getOption('popupContentTemplate')
52
+ return Utils.loadTemplate(
53
+ `<div class="umap-popup-container text">${this.toHTML(feature, template)}</div>`
54
+ )
50
55
  }
51
56
 
52
57
  renderFooter(feature) {
@@ -377,7 +382,8 @@ class Route extends TitleMixin(PopupTemplate) {
377
382
  }).addTo(map)
378
383
  })
379
384
  if (feature.properties.description) {
380
- root.appendChild(Utils.loadTemplate(`<p>${feature.properties.description}</p>`))
385
+ const content = this.toHTML(feature, feature.properties.description)
386
+ root.appendChild(Utils.loadTemplate(`<p>${content}</p>`))
381
387
  }
382
388
  return root
383
389
  }
@@ -69,7 +69,7 @@ class Rule {
69
69
  }
70
70
  // TODO: deal with legacy rules on non typed fields
71
71
  else {
72
- this.cast = this.field.parse
72
+ this.cast = (value) => this.field.parse(value)
73
73
  if (
74
74
  // Special cases where we want to be lousy when checking isNaN without
75
75
  // coercing to a Number first because we handle multiple types.
@@ -9,7 +9,7 @@ import TemplateImporter from '../templates.js'
9
9
  const TOP_BAR_TEMPLATE = `
10
10
  <div class="umap-main-edit-toolbox with-transition dark">
11
11
  <div class="umap-left-edit-toolbox" data-ref="left">
12
- <div class="logo"><a class="" href="/" title="${translate('Go to the homepage')}">uMap</a></div>
12
+ <div class="logo"><a class="" href="#" title="${translate('Go to the homepage')}" data-ref="home">uMap</a></div>
13
13
  <button class="map-name flat truncate" type="button" data-ref="name"></button>
14
14
  <button class="flat truncate" type="button" data-ref="share">
15
15
  <i class="icon icon-16 icon-draft show-on-draft"></i><span class="share-status"></span>
@@ -59,6 +59,7 @@ export class TopBar extends WithTemplate {
59
59
 
60
60
  setup() {
61
61
  this.parent.appendChild(this.element)
62
+ this.elements.home.href = this._umap.urls.get('home')
62
63
  this.elements.name.addEventListener('mouseover', () => {
63
64
  this._umap.tooltip.open({
64
65
  content: translate('Edit the title of the map'),
@@ -0,0 +1,36 @@
1
+ export default class Hash {
2
+ constructor() {
3
+ document.body.addEventListener('mapview:updated', (event) => {
4
+ this._updating = true
5
+ this.update(event.detail)
6
+ })
7
+ window.addEventListener('hashchange', () => this.parse())
8
+ }
9
+
10
+ update({ zoom, latlng }) {
11
+ const [lat, lng] = latlng
12
+ window.location.hash = `#${zoom}/${lat}/${lng}`
13
+ }
14
+
15
+ parse() {
16
+ // Do not parse and re-update the map when we change the hash ourselves
17
+ // after a move from the user.
18
+ if (this._updating) {
19
+ this._updating = false
20
+ return
21
+ }
22
+ let hash = window.location.hash
23
+ if (hash.indexOf('#') === 0) {
24
+ hash = hash.substr(1)
25
+ }
26
+ const args = hash.split('/')
27
+ if (args.length !== 3) return
28
+ const zoom = parseInt(args[0], 10)
29
+ const lat = parseFloat(args[1])
30
+ const lng = parseFloat(args[2])
31
+ if (isNaN(zoom) || isNaN(lat) || isNaN(lng)) return
32
+ document.body.dispatchEvent(
33
+ new CustomEvent('mapview:update', { detail: { zoom, latlng: [lat, lng] } })
34
+ )
35
+ }
36
+ }
@@ -0,0 +1,26 @@
1
+ import { loadTemplate } from '../domutils.js'
2
+
3
+ export default class Loader {
4
+ constructor(parent) {
5
+ this.parent = parent
6
+ this.element = loadTemplate('<div class="umap-loader"></div>')
7
+ this.parent.appendChild(this.element)
8
+ document.body.addEventListener('dataloading', (event) =>
9
+ this.start(event.detail.id)
10
+ )
11
+ document.body.addEventListener('dataload', (event) => this.stop(event.detail.id))
12
+ this._counter = new Set()
13
+ }
14
+
15
+ start(id) {
16
+ this._counter.add(id)
17
+ this.parent.classList.add('umap-loading')
18
+ }
19
+
20
+ stop(id) {
21
+ this._counter.delete(id)
22
+ if (!this._counter.size) {
23
+ this.parent.classList.remove('umap-loading')
24
+ }
25
+ }
26
+ }
@@ -98,6 +98,13 @@ export class Panel {
98
98
  document.body.classList.remove(`panel-${this.className.split(' ')[0]}-on`)
99
99
  this.container.dataset.highlight = null
100
100
  this.onClose()
101
+ Promise.all(
102
+ this.container.getAnimations?.().map((animation) => animation.finished)
103
+ ).then(() => {
104
+ if (!this.isOpen()) {
105
+ this.container.innerHTML = ''
106
+ }
107
+ })
101
108
  }
102
109
 
103
110
  onClose() {
@@ -31,6 +31,8 @@ import * as Utils from './utils.js'
31
31
  import * as DOMUtils from './domutils.js'
32
32
  import { DataLayerManager } from './managers.js'
33
33
  import { Importer as OpenRouteService } from './importers/openrouteservice.js'
34
+ import Loader from './ui/loader.js'
35
+ import Hash from './ui/hash.js'
34
36
 
35
37
  export default class Umap {
36
38
  constructor(element, geojson) {
@@ -110,6 +112,10 @@ export default class Umap {
110
112
  this.properties.homeControl = false
111
113
  }
112
114
 
115
+ this.loader = new Loader(this._leafletMap._container)
116
+ if (this.properties.hash) {
117
+ this.hash = new Hash()
118
+ }
113
119
  this._leafletMap.setup()
114
120
 
115
121
  this.panel = new Panel(this, this._leafletMap)
@@ -683,10 +683,11 @@ export const COLORS = [
683
683
  ]
684
684
 
685
685
  export const LatLngIsValid = (latlng) => {
686
+ const [lat, lng] = Array.isArray(latlng) ? latlng : [latlng.lat, latlng.lng]
686
687
  return (
687
- Number.isFinite(latlng.lat) &&
688
- Math.abs(latlng.lat) <= 90 &&
689
- Number.isFinite(latlng.lng) &&
690
- Math.abs(latlng.lng) <= 180
688
+ Number.isFinite(lat) &&
689
+ Math.abs(lat) <= 90 &&
690
+ Number.isFinite(lng) &&
691
+ Math.abs(lng) <= 180
691
692
  )
692
693
  }