umap-project 1.10.0__py3-none-any.whl → 1.11.1__py3-none-any.whl

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

Potentially problematic release.


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

Files changed (181) hide show
  1. umap/__init__.py +1 -1
  2. umap/admin.py +7 -3
  3. umap/autocomplete.py +3 -5
  4. umap/bin/__init__.py +2 -5
  5. umap/decorators.py +4 -5
  6. umap/forms.py +5 -5
  7. umap/locale/en/LC_MESSAGES/django.po +23 -23
  8. umap/locale/fr/LC_MESSAGES/django.mo +0 -0
  9. umap/locale/fr/LC_MESSAGES/django.po +28 -27
  10. umap/management/commands/generate_js_locale.py +9 -11
  11. umap/management/commands/import_pictograms.py +49 -28
  12. umap/managers.py +5 -3
  13. umap/middleware.py +2 -3
  14. umap/migrations/0001_initial.py +204 -56
  15. umap/migrations/0002_tilelayer_tms.py +3 -4
  16. umap/migrations/0003_add_tilelayer.py +13 -9
  17. umap/migrations/0004_add_licence.py +3 -6
  18. umap/migrations/0005_remove_map_tilelayer.py +3 -4
  19. umap/migrations/0006_auto_20190407_0719.py +6 -5
  20. umap/migrations/0007_auto_20190416_1757.py +13 -5
  21. umap/migrations/0008_alter_map_settings.py +0 -1
  22. umap/migrations/0009_star.py +27 -8
  23. umap/migrations/0010_alter_map_edit_status_alter_map_share_status.py +20 -8
  24. umap/migrations/0011_alter_map_edit_status_alter_map_share_status.py +21 -8
  25. umap/migrations/0014_map_created_at.py +1 -1
  26. umap/migrations/0015_alter_pictogram_pictogram.py +17 -0
  27. umap/migrations/0016_pictogram_category.py +17 -0
  28. umap/models.py +9 -7
  29. umap/settings/base.py +1 -4
  30. umap/static/umap/base.css +59 -20
  31. umap/static/umap/content.css +2 -13
  32. umap/static/umap/favicons/apple-touch-icon.png +0 -0
  33. umap/static/umap/favicons/favicon.ico +0 -0
  34. umap/static/umap/favicons/icon-192.png +0 -0
  35. umap/static/umap/favicons/icon-512.png +0 -0
  36. umap/static/umap/favicons/icon.svg +5 -0
  37. umap/static/umap/img/16-white.svg +20 -9
  38. umap/static/umap/img/24-white.svg +2 -2
  39. umap/static/umap/img/source/16-white.svg +25 -13
  40. umap/static/umap/img/source/24-white.svg +5 -5
  41. umap/static/umap/js/umap.controls.js +15 -23
  42. umap/static/umap/js/umap.core.js +33 -24
  43. umap/static/umap/js/umap.features.js +24 -2
  44. umap/static/umap/js/umap.forms.js +182 -84
  45. umap/static/umap/js/umap.icon.js +19 -14
  46. umap/static/umap/js/umap.js +14 -32
  47. umap/static/umap/js/umap.layer.js +13 -2
  48. umap/static/umap/js/umap.popup.js +21 -0
  49. umap/static/umap/locale/am_ET.js +9 -4
  50. umap/static/umap/locale/am_ET.json +9 -4
  51. umap/static/umap/locale/ar.js +9 -4
  52. umap/static/umap/locale/ar.json +9 -4
  53. umap/static/umap/locale/ast.js +9 -4
  54. umap/static/umap/locale/ast.json +9 -4
  55. umap/static/umap/locale/bg.js +9 -4
  56. umap/static/umap/locale/bg.json +9 -4
  57. umap/static/umap/locale/br.js +20 -15
  58. umap/static/umap/locale/br.json +20 -15
  59. umap/static/umap/locale/ca.js +9 -4
  60. umap/static/umap/locale/ca.json +9 -4
  61. umap/static/umap/locale/cs_CZ.js +9 -4
  62. umap/static/umap/locale/cs_CZ.json +9 -4
  63. umap/static/umap/locale/da.js +9 -4
  64. umap/static/umap/locale/da.json +9 -4
  65. umap/static/umap/locale/de.js +9 -4
  66. umap/static/umap/locale/de.json +9 -4
  67. umap/static/umap/locale/el.js +9 -4
  68. umap/static/umap/locale/el.json +9 -4
  69. umap/static/umap/locale/en.js +9 -4
  70. umap/static/umap/locale/en.json +9 -4
  71. umap/static/umap/locale/en_US.json +9 -4
  72. umap/static/umap/locale/es.js +15 -10
  73. umap/static/umap/locale/es.json +15 -10
  74. umap/static/umap/locale/et.js +9 -4
  75. umap/static/umap/locale/et.json +9 -4
  76. umap/static/umap/locale/fa_IR.js +9 -4
  77. umap/static/umap/locale/fa_IR.json +9 -4
  78. umap/static/umap/locale/fi.js +9 -4
  79. umap/static/umap/locale/fi.json +9 -4
  80. umap/static/umap/locale/fr.js +10 -5
  81. umap/static/umap/locale/fr.json +10 -5
  82. umap/static/umap/locale/gl.js +9 -4
  83. umap/static/umap/locale/gl.json +9 -4
  84. umap/static/umap/locale/he.js +9 -4
  85. umap/static/umap/locale/he.json +9 -4
  86. umap/static/umap/locale/hr.js +9 -4
  87. umap/static/umap/locale/hr.json +9 -4
  88. umap/static/umap/locale/hu.js +64 -59
  89. umap/static/umap/locale/hu.json +64 -59
  90. umap/static/umap/locale/id.js +9 -4
  91. umap/static/umap/locale/id.json +9 -4
  92. umap/static/umap/locale/is.js +9 -4
  93. umap/static/umap/locale/is.json +9 -4
  94. umap/static/umap/locale/it.js +9 -4
  95. umap/static/umap/locale/it.json +9 -4
  96. umap/static/umap/locale/ja.js +9 -4
  97. umap/static/umap/locale/ja.json +9 -4
  98. umap/static/umap/locale/ko.js +9 -4
  99. umap/static/umap/locale/ko.json +9 -4
  100. umap/static/umap/locale/lt.js +9 -4
  101. umap/static/umap/locale/lt.json +9 -4
  102. umap/static/umap/locale/ms.js +15 -10
  103. umap/static/umap/locale/ms.json +15 -10
  104. umap/static/umap/locale/nl.js +9 -4
  105. umap/static/umap/locale/nl.json +9 -4
  106. umap/static/umap/locale/no.js +9 -4
  107. umap/static/umap/locale/no.json +9 -4
  108. umap/static/umap/locale/pl.js +9 -4
  109. umap/static/umap/locale/pl.json +9 -4
  110. umap/static/umap/locale/pl_PL.json +9 -4
  111. umap/static/umap/locale/pt.js +9 -4
  112. umap/static/umap/locale/pt.json +9 -4
  113. umap/static/umap/locale/pt_BR.js +9 -4
  114. umap/static/umap/locale/pt_BR.json +9 -4
  115. umap/static/umap/locale/pt_PT.js +9 -4
  116. umap/static/umap/locale/pt_PT.json +9 -4
  117. umap/static/umap/locale/ro.js +9 -4
  118. umap/static/umap/locale/ro.json +9 -4
  119. umap/static/umap/locale/ru.js +9 -4
  120. umap/static/umap/locale/ru.json +9 -4
  121. umap/static/umap/locale/si.js +55 -20
  122. umap/static/umap/locale/si.json +55 -20
  123. umap/static/umap/locale/sk_SK.js +9 -4
  124. umap/static/umap/locale/sk_SK.json +9 -4
  125. umap/static/umap/locale/sl.js +9 -4
  126. umap/static/umap/locale/sl.json +9 -4
  127. umap/static/umap/locale/sr.js +9 -4
  128. umap/static/umap/locale/sr.json +9 -4
  129. umap/static/umap/locale/sv.js +9 -4
  130. umap/static/umap/locale/sv.json +9 -4
  131. umap/static/umap/locale/th_TH.js +9 -4
  132. umap/static/umap/locale/th_TH.json +9 -4
  133. umap/static/umap/locale/tr.js +9 -4
  134. umap/static/umap/locale/tr.json +9 -4
  135. umap/static/umap/locale/uk_UA.js +9 -4
  136. umap/static/umap/locale/uk_UA.json +9 -4
  137. umap/static/umap/locale/vi.js +9 -4
  138. umap/static/umap/locale/vi.json +9 -4
  139. umap/static/umap/locale/vi_VN.json +9 -4
  140. umap/static/umap/locale/zh.js +9 -4
  141. umap/static/umap/locale/zh.json +9 -4
  142. umap/static/umap/locale/zh_CN.json +9 -4
  143. umap/static/umap/locale/zh_TW.Big5.json +9 -4
  144. umap/static/umap/locale/zh_TW.js +9 -4
  145. umap/static/umap/locale/zh_TW.json +9 -4
  146. umap/static/umap/map.css +104 -21
  147. umap/static/umap/nav.css +0 -1
  148. umap/static/umap/test/Controls.js +0 -1
  149. umap/static/umap/test/Feature.js +29 -0
  150. umap/static/umap/test/Map.Export.js +2 -127
  151. umap/static/umap/test/Util.js +10 -0
  152. umap/static/umap/test/_pre.js +2 -3
  153. umap/static/umap/vendors/formbuilder/Leaflet.FormBuilder.js +13 -2
  154. umap/static/umap/vendors/leaflet/leaflet-src.js +7144 -7144
  155. umap/templates/auth/user_form.html +2 -2
  156. umap/templates/base.html +11 -0
  157. umap/templates/umap/map_table.html +3 -3
  158. umap/templatetags/umap_tags.py +32 -34
  159. umap/tests/base.py +4 -4
  160. umap/tests/conftest.py +3 -6
  161. umap/tests/fixtures/circle.svg +4 -0
  162. umap/tests/fixtures/star.svg +4 -0
  163. umap/tests/integration/test_export_map.py +5 -18
  164. umap/tests/integration/test_picto.py +217 -0
  165. umap/tests/integration/test_slideshow.py +70 -0
  166. umap/tests/settings.py +11 -5
  167. umap/tests/test_datalayer.py +7 -6
  168. umap/tests/test_map.py +6 -5
  169. umap/tests/test_map_views.py +82 -10
  170. umap/tests/test_tilelayer.py +17 -11
  171. umap/tests/test_views.py +36 -9
  172. umap/urls.py +27 -4
  173. umap/utils.py +1 -2
  174. umap/views.py +67 -35
  175. umap/wsgi.py +2 -2
  176. {umap_project-1.10.0.dist-info → umap_project-1.11.1.dist-info}/METADATA +11 -9
  177. {umap_project-1.10.0.dist-info → umap_project-1.11.1.dist-info}/RECORD +180 -170
  178. umap/static/favicon.ico +0 -0
  179. {umap_project-1.10.0.dist-info → umap_project-1.11.1.dist-info}/WHEEL +0 -0
  180. {umap_project-1.10.0.dist-info → umap_project-1.11.1.dist-info}/entry_points.txt +0 -0
  181. {umap_project-1.10.0.dist-info → umap_project-1.11.1.dist-info}/licenses/LICENSE +0 -0
@@ -201,6 +201,16 @@ L.Util.greedyTemplate = (str, data, ignore) => {
201
201
  )
202
202
  }
203
203
 
204
+ L.Util.naturalSort = (a, b) => {
205
+ return a
206
+ .toString()
207
+ .toLowerCase()
208
+ .localeCompare(b.toString().toLowerCase(), L.lang || 'en', {
209
+ sensitivity: 'base',
210
+ numeric: true,
211
+ })
212
+ }
213
+
204
214
  L.Util.sortFeatures = (features, sortKey) => {
205
215
  const sortKeys = (sortKey || 'name').split(',')
206
216
 
@@ -214,19 +224,9 @@ L.Util.sortFeatures = (features, sortKey) => {
214
224
  let score
215
225
  const valA = a.properties[sortKey] || ''
216
226
  const valB = b.properties[sortKey] || ''
217
- if (!valA) {
218
- score = -1
219
- } else if (!valB) {
220
- score = 1
221
- } else {
222
- score = valA
223
- .toString()
224
- .toLowerCase()
225
- .localeCompare(valB.toString().toLowerCase(), L.lang || 'en', {
226
- sensitivity: 'base',
227
- numeric: true,
228
- })
229
- }
227
+ if (!valA) score = -1
228
+ else if (!valB) score = 1
229
+ else score = L.Util.naturalSort(valA, valB)
230
230
  if (score === 0 && sortKeys[i + 1]) return sort(a, b, i + 1)
231
231
  return score * reverse
232
232
  }
@@ -287,6 +287,13 @@ L.Util.copyToClipboard = function (textToCopy) {
287
287
  }
288
288
  }
289
289
 
290
+ L.Util.normalize = function (s) {
291
+ return (s || '')
292
+ .toLowerCase()
293
+ .normalize('NFD')
294
+ .replace(/[\u0300-\u036f]/g, '')
295
+ }
296
+
290
297
  L.DomUtil.add = (tagName, className, container, content) => {
291
298
  const el = L.DomUtil.create(tagName, className, container)
292
299
  if (content) {
@@ -363,21 +370,21 @@ L.DomUtil.after = (target, el) => {
363
370
  }
364
371
 
365
372
  L.DomUtil.RGBRegex = /rgb *\( *([0-9]{1,3}) *, *([0-9]{1,3}) *, *([0-9]{1,3}) *\)/
366
-
367
373
  L.DomUtil.TextColorFromBackgroundColor = (el) => {
368
- let out = '#000000'
369
- if (!window.getComputedStyle) {
370
- return out
371
- }
374
+ return L.DomUtil.contrastedColor(el) ? '#ffffff' : '#000000'
375
+ }
376
+ const _CACHE_CONSTRAST = {}
377
+ L.DomUtil.contrastedColor = (el, bgcolor) => {
378
+ // Return 0 for black and 1 for white
379
+ // bgcolor is a human color, it can be a any keyword (purple…)
380
+ if (typeof _CACHE_CONSTRAST[bgcolor] !== 'undefined') return _CACHE_CONSTRAST[bgcolor]
381
+ let out = 0
372
382
  let rgb = window.getComputedStyle(el).getPropertyValue('background-color')
373
383
  rgb = L.DomUtil.RGBRegex.exec(rgb)
374
- if (!rgb || rgb.length !== 4) {
375
- return out
376
- }
384
+ if (!rgb || rgb.length !== 4) return out
377
385
  rgb = parseInt(rgb[1], 10) + parseInt(rgb[2], 10) + parseInt(rgb[3], 10)
378
- if (rgb < (255 * 3) / 2) {
379
- out = '#ffffff'
380
- }
386
+ if (rgb < (255 * 3) / 2) out = 1
387
+ if (bgcolor) _CACHE_CONSTRAST[bgcolor] = out
381
388
  return out
382
389
  }
383
390
  L.DomEvent.once = (el, types, fn, context) => {
@@ -691,12 +698,14 @@ L.U.Orderable = L.Evented.extend({
691
698
  },
692
699
 
693
700
  onDragOver: function (e) {
701
+ L.DomEvent.stop(e)
694
702
  if (e.preventDefault) e.preventDefault() // Necessary. Allows us to drop.
695
703
  e.dataTransfer.dropEffect = 'move'
696
704
  return false
697
705
  },
698
706
 
699
707
  onDragEnter: function (e) {
708
+ L.DomEvent.stop(e)
700
709
  // e.target is the current hover target.
701
710
  const dst = this.findTarget(e.target)
702
711
  if (!dst || dst === this.src) return
@@ -547,6 +547,14 @@ L.U.Marker = L.Marker.extend({
547
547
  this.setIcon(this.getIcon())
548
548
  },
549
549
 
550
+ highlight: function () {
551
+ L.DomUtil.addClass(this.options.icon.elements.main, 'umap-icon-active')
552
+ },
553
+
554
+ resetHighlight: function () {
555
+ L.DomUtil.removeClass(this.options.icon.elements.main, 'umap-icon-active')
556
+ },
557
+
550
558
  addInteractions: function () {
551
559
  L.U.FeatureMixin.addInteractions.call(this)
552
560
  this.on(
@@ -560,6 +568,8 @@ L.U.Marker = L.Marker.extend({
560
568
  if (!this.isReadOnly()) this.on('mouseover', this._enableDragging)
561
569
  this.on('mouseout', this._onMouseOut)
562
570
  this._popupHandlersAdded = true // prevent Leaflet from binding event on bindPopup
571
+ this.on('popupopen', this.highlight)
572
+ this.on('popupclose', this.resetHighlight)
563
573
  },
564
574
 
565
575
  hasGeom: function () {
@@ -608,6 +618,8 @@ L.U.Marker = L.Marker.extend({
608
618
  _initIcon: function () {
609
619
  this.options.icon = this.getIcon()
610
620
  L.Marker.prototype._initIcon.call(this)
621
+ // Allow to run code when icon is actually part of the DOM
622
+ this.options.icon.onAdd()
611
623
  this.resetTooltip()
612
624
  },
613
625
 
@@ -668,8 +680,8 @@ L.U.Marker = L.Marker.extend({
668
680
  appendEditFieldsets: function (container) {
669
681
  L.U.FeatureMixin.appendEditFieldsets.call(this, container)
670
682
  const coordinatesOptions = [
671
- ['_latlng.lat', { handler: 'FloatInput', label: L._('Latitude'), step: 'any' }],
672
- ['_latlng.lng', { handler: 'FloatInput', label: L._('Longitude'), step: 'any' }],
683
+ ['_latlng.lat', { handler: 'FloatInput', label: L._('Latitude') }],
684
+ ['_latlng.lng', { handler: 'FloatInput', label: L._('Longitude') }],
673
685
  ]
674
686
  const builder = new L.U.FormBuilder(this, coordinatesOptions, {
675
687
  callback: function () {
@@ -814,6 +826,14 @@ L.U.PathMixin = {
814
826
  L.U.FeatureMixin.endEdit.call(this)
815
827
  },
816
828
 
829
+ highlightPath: function () {
830
+ this.parentClass.prototype.setStyle.call(this, {
831
+ fillOpacity: Math.sqrt(this.getDynamicOption('fillOpacity', 1.0)),
832
+ opacity: 1.0,
833
+ weight: 1.3 * this.getDynamicOption('weight'),
834
+ })
835
+ },
836
+
817
837
  _onMouseOver: function () {
818
838
  if (this.map.measureTools && this.map.measureTools.enabled()) {
819
839
  this.map.ui.tooltip({ content: this.getMeasure(), anchor: this })
@@ -827,6 +847,8 @@ L.U.PathMixin = {
827
847
  this.on('mouseover', this._onMouseOver)
828
848
  this.on('edit', this.makeDirty)
829
849
  this.on('drag editable:drag', this._onDrag)
850
+ this.on('popupopen', this.highlightPath)
851
+ this.on('popupclose', this._redraw)
830
852
  },
831
853
 
832
854
  _onDrag: function () {
@@ -1,4 +1,10 @@
1
1
  L.FormBuilder.Element.include({
2
+ undefine: function () {
3
+ L.DomUtil.addClass(this.wrapper, 'undefined')
4
+ this.clear()
5
+ this.sync()
6
+ },
7
+
2
8
  getParentNode: function () {
3
9
  if (this.options.wrapper) {
4
10
  return L.DomUtil.create(
@@ -29,15 +35,10 @@ L.FormBuilder.Element.include({
29
35
  },
30
36
  this
31
37
  )
32
- L.DomEvent.on(
38
+ L.DomEvent.on(undefine, 'click', L.DomEvent.stop).on(
33
39
  undefine,
34
40
  'click',
35
- function (e) {
36
- L.DomEvent.stop(e)
37
- L.DomUtil.addClass(this.wrapper, 'undefined')
38
- this.clear()
39
- this.sync()
40
- },
41
+ this.undefine,
41
42
  this
42
43
  )
43
44
  }
@@ -524,133 +525,230 @@ L.FormBuilder.IconUrl = L.FormBuilder.BlurInput.extend({
524
525
 
525
526
  build: function () {
526
527
  L.FormBuilder.BlurInput.prototype.build.call(this)
527
- // Try to guess if the icon content has been customized, and if yes
528
- // directly display the field
529
- this.input.type = this.value() && !this.value().startsWith('/') ? 'text' : 'hidden'
530
- this.input.placeholder = L._('Symbol or url')
531
- this.buttonsContainer = L.DomUtil.create('div', '')
532
- this.pictogramsContainer = L.DomUtil.create('div', 'umap-pictogram-list')
533
- L.DomUtil.before(this.input, this.buttonsContainer)
534
- L.DomUtil.before(this.input, this.pictogramsContainer)
528
+ this.buttons = L.DomUtil.create('div', '', this.parentNode)
529
+ this.tabs = L.DomUtil.create('div', 'pictogram-tabs', this.parentNode)
530
+ this.body = L.DomUtil.create('div', 'umap-pictogram-body', this.parentNode)
531
+ this.footer = L.DomUtil.create('div', '', this.parentNode)
535
532
  this.udpatePreview()
536
- this.on('define', this.fetchIconList)
533
+ this.on('define', this.onDefine)
537
534
  },
538
535
 
539
- isUrl: function () {
540
- return this.value() && this.value().indexOf('/') !== -1
536
+ onDefine: function () {
537
+ this.buttons.innerHTML = ''
538
+ this.footer.innerHTML = ''
539
+ this.buildTabs()
540
+ const value = this.value()
541
+ if (!value || value.startsWith('/')) this.showSymbolsTab()
542
+ else if (value.startsWith('http')) this.showURLTab()
543
+ else this.showCharsTab()
544
+ const closeButton = L.DomUtil.createButton(
545
+ 'button action-button',
546
+ this.footer,
547
+ L._('Close'),
548
+ function (e) {
549
+ this.body.innerHTML = ''
550
+ this.tabs.innerHTML = ''
551
+ this.footer.innerHTML = ''
552
+ if (this.isDefault()) this.undefine(e)
553
+ else this.udpatePreview()
554
+ },
555
+ this
556
+ )
557
+ },
558
+
559
+ buildTabs: function () {
560
+ this.tabs.innerHTML = ''
561
+ const symbol = L.DomUtil.add(
562
+ 'button',
563
+ 'flat tab-symbols',
564
+ this.tabs,
565
+ L._('Symbol')
566
+ ),
567
+ char = L.DomUtil.add(
568
+ 'button',
569
+ 'flat tab-chars',
570
+ this.tabs,
571
+ L._('Emoji & Character')
572
+ )
573
+ url = L.DomUtil.add('button', 'flat tab-url', this.tabs, L._('URL'))
574
+ L.DomEvent.on(symbol, 'click', L.DomEvent.stop).on(
575
+ symbol,
576
+ 'click',
577
+ this.showSymbolsTab,
578
+ this
579
+ )
580
+ L.DomEvent.on(char, 'click', L.DomEvent.stop).on(
581
+ char,
582
+ 'click',
583
+ this.showCharsTab,
584
+ this
585
+ )
586
+ L.DomEvent.on(url, 'click', L.DomEvent.stop).on(url, 'click', this.showURLTab, this)
587
+ },
588
+
589
+ openTab: function (name) {
590
+ const els = this.tabs.querySelectorAll('button')
591
+ for (let el of els) {
592
+ L.DomUtil.removeClass(el, 'on')
593
+ }
594
+ let el = this.tabs.querySelector(`.tab-${name}`)
595
+ L.DomUtil.addClass(el, 'on')
596
+ this.body.innerHTML = ''
597
+ },
598
+
599
+ isPath: function () {
600
+ const value = this.value()
601
+ return value && value.length && value.startsWith('/')
602
+ },
603
+
604
+ isRemoteUrl: function () {
605
+ const value = this.value()
606
+ return value && value.length && value.startsWith('http')
607
+ },
608
+
609
+ isImg: function () {
610
+ return this.isPath() || this.isRemoteUrl()
541
611
  },
542
612
 
543
613
  udpatePreview: function () {
614
+ this.buttons.innerHTML = ''
615
+ if (this.isDefault()) return
544
616
  if (!L.Util.hasVar(this.value())) {
545
617
  // Do not try to render URL with variables
546
- if (this.isUrl()) {
547
- const img = L.DomUtil.create(
548
- 'img',
549
- '',
550
- L.DomUtil.create('div', 'umap-icon-choice', this.buttonsContainer)
551
- )
618
+ const box = L.DomUtil.create('div', 'umap-pictogram-choice', this.buttons)
619
+ L.DomEvent.on(box, 'click', this.onDefine, this)
620
+ if (this.isImg()) {
621
+ const img = L.DomUtil.create('img', '', box)
552
622
  img.src = this.value()
553
- L.DomEvent.on(img, 'click', this.fetchIconList, this)
554
623
  } else {
555
- const el = L.DomUtil.create(
556
- 'span',
557
- '',
558
- L.DomUtil.create('div', 'umap-icon-choice', this.buttonsContainer)
559
- )
624
+ const el = L.DomUtil.create('span', '', box)
560
625
  el.textContent = this.value()
561
- L.DomEvent.on(el, 'click', this.fetchIconList, this)
562
626
  }
563
627
  }
564
628
  this.button = L.DomUtil.createButton(
565
629
  'button action-button',
566
- this.buttonsContainer,
630
+ this.buttons,
567
631
  this.value() ? L._('Change') : L._('Add'),
568
- this.fetchIconList,
632
+ this.onDefine,
569
633
  this
570
634
  )
571
635
  },
572
636
 
573
- addIconPreview: function (pictogram) {
574
- const baseClass = 'umap-icon-choice',
637
+ addIconPreview: function (pictogram, parent) {
638
+ const baseClass = 'umap-pictogram-choice',
575
639
  value = pictogram.src,
576
- className = value === this.value() ? `${baseClass} selected` : baseClass,
577
- container = L.DomUtil.create('div', className, this.pictogramsContainer),
640
+ search = L.Util.normalize(this.searchInput.value),
641
+ title = pictogram.attribution
642
+ ? `${pictogram.name} — © ${pictogram.attribution}`
643
+ : pictogram.name
644
+ if (search && L.Util.normalize(title).indexOf(search) === -1) return
645
+ const className = value === this.value() ? `${baseClass} selected` : baseClass,
646
+ container = L.DomUtil.create('div', className, parent),
578
647
  img = L.DomUtil.create('img', '', container)
579
648
  img.src = value
580
- if (pictogram.name && pictogram.attribution) {
581
- container.title = `${pictogram.name} — © ${pictogram.attribution}`
582
- }
649
+ container.title = title
583
650
  L.DomEvent.on(
584
651
  container,
585
652
  'click',
586
653
  function (e) {
587
654
  this.input.value = value
588
655
  this.sync()
589
- this.unselectAll(this.pictogramsContainer)
656
+ this.unselectAll(this.grid)
590
657
  L.DomUtil.addClass(container, 'selected')
591
658
  },
592
659
  this
593
660
  )
661
+ return true // Icon has been added (not filtered)
594
662
  },
595
663
 
596
664
  clear: function () {
597
665
  this.input.value = ''
598
- this.unselectAll(this.pictogramsContainer)
666
+ this.unselectAll(this.body)
599
667
  this.sync()
600
- this.pictogramsContainer.innerHTML = ''
668
+ this.body.innerHTML = ''
601
669
  this.udpatePreview()
602
670
  },
603
671
 
604
- search: function (e) {
605
- const icons = [...this.parentNode.querySelectorAll('.umap-icon-choice')],
606
- search = this.searchInput.value.toLowerCase()
607
- icons.forEach((el) => {
608
- if (el.title.toLowerCase().indexOf(search) != -1) el.style.display = 'block'
609
- else el.style.display = 'none'
610
- })
672
+ addCategory: function (category, items) {
673
+ const parent = L.DomUtil.create('div', 'umap-pictogram-category'),
674
+ title = L.DomUtil.add('h6', '', parent, category),
675
+ grid = L.DomUtil.create('div', 'umap-pictogram-grid', parent)
676
+ let status = false
677
+ for (let item of items) {
678
+ status = this.addIconPreview(item, grid) || status
679
+ }
680
+ if (status) this.grid.appendChild(parent)
611
681
  },
612
682
 
613
- buildIconList: function (data) {
614
- this.searchInput = L.DomUtil.create('input', '', this.pictogramsContainer)
683
+ buildSymbolsList: function () {
684
+ this.grid.innerHTML = ''
685
+ const categories = {}
686
+ let category
687
+ for (const props of this.pictogram_list) {
688
+ category = props.category || L._('Generic')
689
+ categories[category] = categories[category] || []
690
+ categories[category].push(props)
691
+ }
692
+ const sorted = Object.entries(categories).toSorted(([a], [b]) =>
693
+ L.Util.naturalSort(a, b)
694
+ )
695
+ for (let [category, items] of sorted) {
696
+ this.addCategory(category, items)
697
+ }
698
+ },
699
+
700
+ isDefault: function () {
701
+ return !this.value() || this.value() === this.obj.getMap().options.default_iconUrl
702
+ },
703
+
704
+ showSymbolsTab: function () {
705
+ this.openTab('symbols')
706
+ this.searchInput = L.DomUtil.create('input', '', this.body)
615
707
  this.searchInput.type = 'search'
616
708
  this.searchInput.placeholder = L._('Search')
617
- L.DomEvent.on(this.searchInput, 'input', this.search, this)
618
- for (const idx in data.pictogram_list) {
619
- this.addIconPreview(data.pictogram_list[idx])
709
+ this.grid = L.DomUtil.create('div', '', this.body)
710
+ L.DomEvent.on(this.searchInput, 'input', this.buildSymbolsList, this)
711
+ if (this.pictogram_list) {
712
+ this.buildSymbolsList()
713
+ } else {
714
+ this.builder.map.get(this.builder.map.options.urls.pictogram_list_json, {
715
+ callback: (data) => {
716
+ this.pictogram_list = data.pictogram_list
717
+ this.buildSymbolsList()
718
+ },
719
+ context: this,
720
+ })
620
721
  }
621
- const closeButton = L.DomUtil.createButton(
622
- 'button action-button',
623
- this.pictogramsContainer,
624
- L._('Close'),
625
- function (e) {
626
- this.pictogramsContainer.innerHTML = ''
627
- this.udpatePreview()
628
- },
629
- this
630
- )
631
- closeButton.style.display = 'block'
632
- closeButton.style.clear = 'both'
722
+ },
633
723
 
634
- const customButton = L.DomUtil.createButton(
635
- 'flat',
636
- this.pictogramsContainer,
637
- L._('Toggle direct input (advanced)'),
638
- function (e) {
639
- this.input.type = this.input.type === 'text' ? 'hidden' : 'text'
640
- },
641
- this
642
- )
643
- this.builder.map.help.button(customButton, 'formatIconSymbol')
724
+ showCharsTab: function () {
725
+ this.openTab('chars')
726
+ const value = !this.isImg() ? this.value() : null
727
+ const input = this.buildInput(this.body, value)
728
+ input.placeholder = L._('Type char or paste emoji')
729
+ input.type = 'text'
644
730
  },
645
731
 
646
- fetchIconList: function (e) {
647
- // Clean parent element before calling ajax, to prevent blinking
648
- this.pictogramsContainer.innerHTML = ''
649
- this.buttonsContainer.innerHTML = ''
650
- this.builder.map.get(this.builder.map.options.urls.pictogram_list_json, {
651
- callback: this.buildIconList,
652
- context: this,
732
+ showURLTab: function () {
733
+ this.openTab('url')
734
+ const value = this.isRemoteUrl() ? this.value() : null
735
+ const input = this.buildInput(this.body, value)
736
+ input.placeholder = L._('Add image URL')
737
+ input.type = 'url'
738
+ },
739
+
740
+ buildInput: function (parent, value) {
741
+ const input = L.DomUtil.create('input', 'blur', parent)
742
+ const button = L.DomUtil.create('span', 'button blur-button', parent)
743
+ if (value) input.value = value
744
+ L.DomEvent.on(input, 'blur', () => {
745
+ // Do not clear this.input when focus-blur
746
+ // empty input
747
+ if (input.value === value) return
748
+ this.input.value = input.value
749
+ this.sync()
653
750
  })
751
+ return input
654
752
  },
655
753
 
656
754
  unselectAll: function (container) {
@@ -38,6 +38,8 @@ L.U.Icon = L.DivIcon.extend({
38
38
  formatUrl: function (url, feature) {
39
39
  return L.Util.greedyTemplate(url || '', feature ? feature.extendedProperties() : {})
40
40
  },
41
+
42
+ onAdd: function () {},
41
43
  })
42
44
 
43
45
  L.U.Icon.Default = L.U.Icon.extend({
@@ -63,6 +65,19 @@ L.U.Icon.Default = L.U.Icon.extend({
63
65
  this.elements.arrow.style.opacity = opacity
64
66
  },
65
67
 
68
+ onAdd: function () {
69
+ const src = this._getIconUrl('icon')
70
+ // Decide whether to switch svg to white or not, but do it
71
+ // only for internal SVG, as invert could do weird things
72
+ if (src.startsWith('/') && src.endsWith('.svg')) {
73
+ const bgcolor = this._getColor()
74
+ // Must be called after icon container is added to the DOM
75
+ if (L.DomUtil.contrastedColor(this.elements.container, bgcolor)) {
76
+ this.elements.img.style.filter = 'invert(1)'
77
+ }
78
+ }
79
+ },
80
+
66
81
  createIcon: function () {
67
82
  this.elements = {}
68
83
  this.elements.main = L.DomUtil.create('div')
@@ -76,9 +91,9 @@ L.U.Icon.Default = L.U.Icon.extend({
76
91
  if (src) {
77
92
  // An url.
78
93
  if (
79
- src.indexOf('http') === 0 ||
80
- src.indexOf('/') === 0 ||
81
- src.indexOf('data:image') === 0
94
+ src.startsWith('http') ||
95
+ src.startsWith('/') ||
96
+ src.startsWith('data:image')
82
97
  ) {
83
98
  this.elements.img = L.DomUtil.create('img', null, this.elements.container)
84
99
  this.elements.img.src = src
@@ -95,7 +110,6 @@ L.U.Icon.Default = L.U.Icon.extend({
95
110
  L.U.Icon.Circle = L.U.Icon.extend({
96
111
  initialize: function (map, options) {
97
112
  const default_options = {
98
- iconAnchor: new L.Point(6, 6),
99
113
  popupAnchor: new L.Point(0, -6),
100
114
  tooltipAnchor: new L.Point(6, 0),
101
115
  className: 'umap-circle-icon',
@@ -165,7 +179,6 @@ L.U.Icon.Ball = L.U.Icon.Default.extend({
165
179
  },
166
180
  })
167
181
 
168
- const _CACHE_COLOR = {}
169
182
  L.U.Icon.Cluster = L.DivIcon.extend({
170
183
  options: {
171
184
  iconSize: [40, 40],
@@ -192,14 +205,6 @@ L.U.Icon.Cluster = L.DivIcon.extend({
192
205
  if (this.datalayer.options.cluster && this.datalayer.options.cluster.textColor) {
193
206
  color = this.datalayer.options.cluster.textColor
194
207
  }
195
- if (!color) {
196
- if (typeof _CACHE_COLOR[backgroundColor] === 'undefined') {
197
- color = L.DomUtil.TextColorFromBackgroundColor(el)
198
- _CACHE_COLOR[backgroundColor] = color
199
- } else {
200
- color = _CACHE_COLOR[backgroundColor]
201
- }
202
- }
203
- return color
208
+ return color || L.DomUtil.TextColorFromBackgroundColor(el, backgroundColor)
204
209
  },
205
210
  })
@@ -272,7 +272,10 @@ L.U.Map.include({
272
272
  url.searchParams.delete('edit')
273
273
  history.pushState({}, '', url)
274
274
  }
275
- if (L.Util.queryString('download')) this.download()
275
+ if (L.Util.queryString('download'))
276
+ window.location = L.Util.template(this.options.urls.map_download, {
277
+ map_id: this.options.umap_id,
278
+ })
276
279
  })
277
280
 
278
281
  window.onbeforeunload = () => this.isDirty || null
@@ -396,7 +399,6 @@ L.U.Map.include({
396
399
  },
397
400
 
398
401
  loadDatalayers: function (force) {
399
- force = force || L.Util.queryString('download') // In case we are in download mode, let's go strait to loading all data
400
402
  const total = this.datalayers_index.length
401
403
  // toload => datalayer metadata remaining to load (synchronous)
402
404
  // dataToload => datalayer data remaining to load (asynchronous)
@@ -686,7 +688,11 @@ L.U.Map.include({
686
688
  // FIXME An invalid hash will cause the load to fail
687
689
  this._hash.update()
688
690
  } else if (this.options.defaultView === 'locate' && !this.options.noControl) {
689
- this.once('locationerror', this._setDefaultCenter)
691
+ // When using locate as default map view AND activating easing
692
+ // Leaflet.locate will ask the map view to compute transition to user
693
+ // position, so in this case we do need a default center, so let's
694
+ // set it anyway
695
+ this._setDefaultCenter()
690
696
  this._controls.locate.start()
691
697
  } else if (this.options.defaultView === 'data') {
692
698
  this.onceDataLoaded(() => {
@@ -824,14 +830,8 @@ L.U.Map.include({
824
830
  })
825
831
  },
826
832
 
827
- fullDownload: function () {
828
- // Make sure all data is loaded before downloading
829
- this.once('dataloaded', () => this.download())
830
- this.loadDatalayers(true) // Force load
831
- },
832
-
833
833
  format: function (mode) {
834
- const type = this.EXPORT_TYPES[mode || 'umap']
834
+ const type = this.EXPORT_TYPES[mode]
835
835
  const content = type.formatter(this)
836
836
  let name = this.options.name || 'data'
837
837
  name = name.replace(/[^a-z0-9]/gi, '_').toLowerCase()
@@ -1074,24 +1074,6 @@ L.U.Map.include({
1074
1074
  return properties
1075
1075
  },
1076
1076
 
1077
- serialize: function () {
1078
- // Do not use local path during unit tests
1079
- const uri = window.location.protocol === 'file:' ? null : window.location.href
1080
- const umapfile = {
1081
- type: 'umap',
1082
- uri: uri,
1083
- properties: this.exportOptions(),
1084
- geometry: this.geometry(),
1085
- layers: [],
1086
- }
1087
-
1088
- this.eachDataLayer((datalayer) => {
1089
- umapfile.layers.push(datalayer.umapGeoJSON())
1090
- })
1091
-
1092
- return JSON.stringify(umapfile, null, 2)
1093
- },
1094
-
1095
1077
  saveSelf: function () {
1096
1078
  const geojson = {
1097
1079
  type: 'Feature',
@@ -1766,16 +1748,16 @@ L.U.Map.include({
1766
1748
  this.permissions.addOwnerLink('span', container)
1767
1749
  if (this.options.captionMenus) {
1768
1750
  L.DomUtil.createButton(
1769
- 'umap-about-link',
1751
+ 'umap-about-link flat',
1770
1752
  container,
1771
- ` — ${L._('About')}`,
1753
+ L._('About'),
1772
1754
  this.displayCaption,
1773
1755
  this
1774
1756
  )
1775
1757
  L.DomUtil.createButton(
1776
- 'umap-open-browser-link',
1758
+ 'umap-open-browser-link flat',
1777
1759
  container,
1778
- ` | ${L._('Browse data')}`,
1760
+ L._('Browse data'),
1779
1761
  this.openBrowser,
1780
1762
  this
1781
1763
  )