umap-project 3.3.2__py3-none-any.whl → 3.4.0__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 (242) hide show
  1. umap/__init__.py +1 -1
  2. umap/context_processors.py +4 -1
  3. umap/locale/cs_CZ/LC_MESSAGES/django.mo +0 -0
  4. umap/locale/cs_CZ/LC_MESSAGES/django.po +43 -33
  5. umap/locale/da/LC_MESSAGES/django.mo +0 -0
  6. umap/locale/da/LC_MESSAGES/django.po +43 -33
  7. umap/locale/de/LC_MESSAGES/django.mo +0 -0
  8. umap/locale/de/LC_MESSAGES/django.po +64 -53
  9. umap/locale/el/LC_MESSAGES/django.mo +0 -0
  10. umap/locale/el/LC_MESSAGES/django.po +35 -29
  11. umap/locale/en/LC_MESSAGES/django.po +47 -41
  12. umap/locale/es/LC_MESSAGES/django.mo +0 -0
  13. umap/locale/es/LC_MESSAGES/django.po +43 -33
  14. umap/locale/et/LC_MESSAGES/django.mo +0 -0
  15. umap/locale/et/LC_MESSAGES/django.po +58 -54
  16. umap/locale/eu/LC_MESSAGES/django.mo +0 -0
  17. umap/locale/eu/LC_MESSAGES/django.po +43 -33
  18. umap/locale/fa_IR/LC_MESSAGES/django.mo +0 -0
  19. umap/locale/fa_IR/LC_MESSAGES/django.po +43 -33
  20. umap/locale/fr/LC_MESSAGES/django.mo +0 -0
  21. umap/locale/fr/LC_MESSAGES/django.po +36 -30
  22. umap/locale/gl/LC_MESSAGES/django.mo +0 -0
  23. umap/locale/gl/LC_MESSAGES/django.po +43 -33
  24. umap/locale/hu/LC_MESSAGES/django.mo +0 -0
  25. umap/locale/hu/LC_MESSAGES/django.po +35 -29
  26. umap/locale/is/LC_MESSAGES/django.mo +0 -0
  27. umap/locale/is/LC_MESSAGES/django.po +43 -33
  28. umap/locale/it/LC_MESSAGES/django.mo +0 -0
  29. umap/locale/it/LC_MESSAGES/django.po +43 -33
  30. umap/locale/nl/LC_MESSAGES/django.mo +0 -0
  31. umap/locale/nl/LC_MESSAGES/django.po +35 -29
  32. umap/locale/pl/LC_MESSAGES/django.mo +0 -0
  33. umap/locale/pl/LC_MESSAGES/django.po +114 -103
  34. umap/locale/pt/LC_MESSAGES/django.mo +0 -0
  35. umap/locale/pt/LC_MESSAGES/django.po +43 -33
  36. umap/locale/th_TH/LC_MESSAGES/django.mo +0 -0
  37. umap/locale/th_TH/LC_MESSAGES/django.po +310 -109
  38. umap/locale/zh_TW/LC_MESSAGES/django.mo +0 -0
  39. umap/locale/zh_TW/LC_MESSAGES/django.po +80 -70
  40. umap/management/commands/switch_user.py +2 -2
  41. umap/migrations/0018_datalayer_uuid.py +1 -1
  42. umap/models.py +7 -3
  43. umap/settings/local.py.sample +1 -1
  44. umap/static/umap/base.css +89 -32
  45. umap/static/umap/content.css +129 -33
  46. umap/static/umap/css/bar.css +82 -20
  47. umap/static/umap/css/browser.css +163 -0
  48. umap/static/umap/css/contextmenu.css +15 -0
  49. umap/static/umap/css/dialog.css +36 -16
  50. umap/static/umap/css/form.css +123 -33
  51. umap/static/umap/css/icon.css +46 -3
  52. umap/static/umap/css/panel.css +7 -3
  53. umap/static/umap/css/popup.css +34 -8
  54. umap/static/umap/css/tooltip.css +8 -4
  55. umap/static/umap/img/16-white.svg +26 -8
  56. umap/static/umap/img/16.svg +1 -1
  57. umap/static/umap/img/source/16-white.svg +36 -18
  58. umap/static/umap/img/source/16.svg +1 -1
  59. umap/static/umap/js/components/alerts/alert.css +69 -31
  60. umap/static/umap/js/components/alerts/alert.js +20 -2
  61. umap/static/umap/js/components/base.js +1 -1
  62. umap/static/umap/js/modules/browser.js +69 -61
  63. umap/static/umap/js/modules/caption.js +10 -7
  64. umap/static/umap/js/modules/data/features.js +89 -63
  65. umap/static/umap/js/modules/data/fields.js +446 -0
  66. umap/static/umap/js/modules/data/layer.js +116 -196
  67. umap/static/umap/js/modules/domutils.js +109 -0
  68. umap/static/umap/js/modules/filters.js +780 -0
  69. umap/static/umap/js/modules/form/builder.js +8 -5
  70. umap/static/umap/js/modules/form/fields.js +111 -221
  71. umap/static/umap/js/modules/formatter.js +24 -1
  72. umap/static/umap/js/modules/help.js +4 -3
  73. umap/static/umap/js/modules/i18n.js +1 -1
  74. umap/static/umap/js/modules/importer.js +1 -1
  75. umap/static/umap/js/modules/importers/opendata.js +15 -0
  76. umap/static/umap/js/modules/importers/openrouteservice.js +6 -1
  77. umap/static/umap/js/modules/managers.js +2 -1
  78. umap/static/umap/js/modules/permissions.js +39 -31
  79. umap/static/umap/js/modules/rendering/controls.js +11 -9
  80. umap/static/umap/js/modules/rendering/icon.js +3 -8
  81. umap/static/umap/js/modules/rendering/layers/base.js +3 -3
  82. umap/static/umap/js/modules/rendering/layers/classified.js +18 -11
  83. umap/static/umap/js/modules/rendering/layers/cluster.js +23 -11
  84. umap/static/umap/js/modules/rendering/layers/heat.js +27 -21
  85. umap/static/umap/js/modules/rendering/map.js +1 -0
  86. umap/static/umap/js/modules/rendering/template.js +50 -23
  87. umap/static/umap/js/modules/rendering/ui.js +33 -25
  88. umap/static/umap/js/modules/rules.js +38 -44
  89. umap/static/umap/js/modules/schema.js +3 -6
  90. umap/static/umap/js/modules/share.js +5 -4
  91. umap/static/umap/js/modules/tableeditor.js +50 -38
  92. umap/static/umap/js/modules/templates.js +2 -3
  93. umap/static/umap/js/modules/ui/bar.js +55 -23
  94. umap/static/umap/js/modules/ui/dialog.js +38 -27
  95. umap/static/umap/js/modules/ui/panel.js +23 -8
  96. umap/static/umap/js/modules/ui/tooltip.js +6 -5
  97. umap/static/umap/js/modules/umap.js +158 -51
  98. umap/static/umap/js/modules/utils.js +24 -2
  99. umap/static/umap/js/umap.core.js +1 -110
  100. umap/static/umap/locale/am_ET.js +52 -17
  101. umap/static/umap/locale/am_ET.json +52 -17
  102. umap/static/umap/locale/ar.js +52 -17
  103. umap/static/umap/locale/ar.json +52 -17
  104. umap/static/umap/locale/ast.js +52 -17
  105. umap/static/umap/locale/ast.json +52 -17
  106. umap/static/umap/locale/bg.js +52 -17
  107. umap/static/umap/locale/bg.json +52 -17
  108. umap/static/umap/locale/br.js +48 -22
  109. umap/static/umap/locale/br.json +48 -22
  110. umap/static/umap/locale/ca.js +52 -17
  111. umap/static/umap/locale/ca.json +52 -17
  112. umap/static/umap/locale/cs_CZ.js +52 -17
  113. umap/static/umap/locale/cs_CZ.json +52 -17
  114. umap/static/umap/locale/da.js +54 -17
  115. umap/static/umap/locale/da.json +54 -17
  116. umap/static/umap/locale/de.js +102 -67
  117. umap/static/umap/locale/de.json +102 -67
  118. umap/static/umap/locale/el.js +52 -17
  119. umap/static/umap/locale/el.json +52 -17
  120. umap/static/umap/locale/en.js +54 -16
  121. umap/static/umap/locale/en.json +54 -16
  122. umap/static/umap/locale/en_US.json +52 -17
  123. umap/static/umap/locale/es.js +54 -17
  124. umap/static/umap/locale/es.json +54 -17
  125. umap/static/umap/locale/et.js +91 -56
  126. umap/static/umap/locale/et.json +91 -56
  127. umap/static/umap/locale/eu.js +84 -49
  128. umap/static/umap/locale/eu.json +84 -49
  129. umap/static/umap/locale/fa_IR.js +52 -17
  130. umap/static/umap/locale/fa_IR.json +52 -17
  131. umap/static/umap/locale/fi.js +52 -17
  132. umap/static/umap/locale/fi.json +52 -17
  133. umap/static/umap/locale/fr.js +54 -17
  134. umap/static/umap/locale/fr.json +54 -17
  135. umap/static/umap/locale/gl.js +52 -17
  136. umap/static/umap/locale/gl.json +52 -17
  137. umap/static/umap/locale/he.js +52 -17
  138. umap/static/umap/locale/he.json +52 -17
  139. umap/static/umap/locale/hr.js +52 -17
  140. umap/static/umap/locale/hr.json +52 -17
  141. umap/static/umap/locale/hu.js +59 -24
  142. umap/static/umap/locale/hu.json +59 -24
  143. umap/static/umap/locale/id.js +52 -17
  144. umap/static/umap/locale/id.json +52 -17
  145. umap/static/umap/locale/is.js +52 -17
  146. umap/static/umap/locale/is.json +52 -17
  147. umap/static/umap/locale/it.js +52 -17
  148. umap/static/umap/locale/it.json +52 -17
  149. umap/static/umap/locale/ja.js +52 -17
  150. umap/static/umap/locale/ja.json +52 -17
  151. umap/static/umap/locale/ko.js +52 -17
  152. umap/static/umap/locale/ko.json +52 -17
  153. umap/static/umap/locale/lt.js +52 -17
  154. umap/static/umap/locale/lt.json +52 -17
  155. umap/static/umap/locale/ms.js +52 -17
  156. umap/static/umap/locale/ms.json +52 -17
  157. umap/static/umap/locale/nl.js +52 -17
  158. umap/static/umap/locale/nl.json +52 -17
  159. umap/static/umap/locale/no.js +52 -17
  160. umap/static/umap/locale/no.json +52 -17
  161. umap/static/umap/locale/pl.js +53 -17
  162. umap/static/umap/locale/pl.json +53 -17
  163. umap/static/umap/locale/pl_PL.json +52 -17
  164. umap/static/umap/locale/pt.js +52 -17
  165. umap/static/umap/locale/pt.json +52 -17
  166. umap/static/umap/locale/pt_BR.js +52 -17
  167. umap/static/umap/locale/pt_BR.json +52 -17
  168. umap/static/umap/locale/pt_PT.js +52 -17
  169. umap/static/umap/locale/pt_PT.json +52 -17
  170. umap/static/umap/locale/ro.js +52 -17
  171. umap/static/umap/locale/ro.json +52 -17
  172. umap/static/umap/locale/ru.js +52 -17
  173. umap/static/umap/locale/ru.json +52 -17
  174. umap/static/umap/locale/si.js +1 -1
  175. umap/static/umap/locale/si.json +1 -1
  176. umap/static/umap/locale/sk_SK.js +52 -17
  177. umap/static/umap/locale/sk_SK.json +52 -17
  178. umap/static/umap/locale/sl.js +52 -17
  179. umap/static/umap/locale/sl.json +52 -17
  180. umap/static/umap/locale/sr.js +52 -17
  181. umap/static/umap/locale/sr.json +52 -17
  182. umap/static/umap/locale/sv.js +52 -17
  183. umap/static/umap/locale/sv.json +52 -17
  184. umap/static/umap/locale/th_TH.js +52 -17
  185. umap/static/umap/locale/th_TH.json +52 -17
  186. umap/static/umap/locale/tr.js +52 -17
  187. umap/static/umap/locale/tr.json +52 -17
  188. umap/static/umap/locale/uk_UA.js +52 -17
  189. umap/static/umap/locale/uk_UA.json +52 -17
  190. umap/static/umap/locale/vi.js +52 -17
  191. umap/static/umap/locale/vi.json +52 -17
  192. umap/static/umap/locale/vi_VN.json +52 -17
  193. umap/static/umap/locale/zh.js +52 -17
  194. umap/static/umap/locale/zh.json +52 -17
  195. umap/static/umap/locale/zh_CN.json +52 -17
  196. umap/static/umap/locale/zh_TW.Big5.json +52 -17
  197. umap/static/umap/locale/zh_TW.js +53 -17
  198. umap/static/umap/locale/zh_TW.json +53 -17
  199. umap/static/umap/map.css +63 -226
  200. umap/static/umap/unittests/utils.js +18 -0
  201. umap/static/umap/vars.css +23 -5
  202. umap/templates/umap/components/alerts/alert.html +32 -29
  203. umap/templates/umap/css.html +2 -1
  204. umap/templates/umap/login_popup_end.html +18 -9
  205. umap/templates/umap/user_map_table.html +7 -2
  206. umap/tests/integration/conftest.py +10 -6
  207. umap/tests/integration/test_anonymous_owned_map.py +90 -37
  208. umap/tests/integration/test_basics.py +25 -1
  209. umap/tests/integration/test_browser.py +37 -0
  210. umap/tests/integration/test_cluster.py +110 -0
  211. umap/tests/integration/test_conditional_rules.py +107 -52
  212. umap/tests/integration/test_datalayer.py +9 -16
  213. umap/tests/integration/test_draw_polygon.py +6 -0
  214. umap/tests/integration/test_draw_polyline.py +11 -0
  215. umap/tests/integration/test_edit_marker.py +12 -1
  216. umap/tests/integration/test_export_map.py +19 -0
  217. umap/tests/integration/test_fields.py +541 -0
  218. umap/tests/integration/test_filters.py +616 -0
  219. umap/tests/integration/test_iframe.py +1 -1
  220. umap/tests/integration/test_import.py +38 -42
  221. umap/tests/integration/test_map_preview.py +1 -1
  222. umap/tests/integration/test_picto.py +1 -1
  223. umap/tests/integration/test_popup.py +31 -0
  224. umap/tests/integration/test_remote_data.py +60 -4
  225. umap/tests/integration/test_save.py +1 -1
  226. umap/tests/integration/test_share.py +4 -4
  227. umap/tests/integration/test_tableeditor.py +31 -7
  228. umap/tests/integration/test_websocket_sync.py +71 -20
  229. umap/tests/test_dashboard.py +11 -1
  230. umap/tests/test_statics.py +2 -2
  231. umap/tests/test_utils.py +19 -2
  232. umap/tests/test_views.py +1 -1
  233. umap/urls.py +1 -0
  234. umap/utils.py +8 -1
  235. umap/views.py +5 -0
  236. {umap_project-3.3.2.dist-info → umap_project-3.4.0.dist-info}/METADATA +15 -15
  237. {umap_project-3.3.2.dist-info → umap_project-3.4.0.dist-info}/RECORD +240 -236
  238. umap/static/umap/js/modules/facets.js +0 -164
  239. umap/tests/integration/test_facets_browser.py +0 -279
  240. {umap_project-3.3.2.dist-info → umap_project-3.4.0.dist-info}/WHEEL +0 -0
  241. {umap_project-3.3.2.dist-info → umap_project-3.4.0.dist-info}/entry_points.txt +0 -0
  242. {umap_project-3.3.2.dist-info → umap_project-3.4.0.dist-info}/licenses/LICENSE +0 -0
@@ -7,7 +7,7 @@ export class DataLayerManager extends Object {
7
7
  active() {
8
8
  return Object.values(this)
9
9
  .filter((datalayer) => !datalayer.isDeleted)
10
- .sort((a, b) => a.rank > b.rank)
10
+ .sort((a, b) => a.rank - b.rank)
11
11
  }
12
12
  reverse() {
13
13
  return this.active().reverse()
@@ -59,6 +59,7 @@ export class FeatureManager extends Map {
59
59
  if (this.has(feature.id)) {
60
60
  console.error('Duplicate id', feature, this.get(feature.id))
61
61
  feature.id = Utils.generateId()
62
+ feature.datalayer._migrated = true
62
63
  }
63
64
  this.set(feature.id, feature)
64
65
  }
@@ -3,6 +3,7 @@ import { uMapAlert as Alert } from '../components/alerts/alert.js'
3
3
  import { MutatingForm } from './form/builder.js'
4
4
  import { translate } from './i18n.js'
5
5
  import * as Utils from './utils.js'
6
+ import * as DOMUtils from './domutils.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.
@@ -42,12 +43,42 @@ export class MapPermissions {
42
43
  }
43
44
 
44
45
  isAnonymousMap() {
45
- return !this._umap.properties.permissions.owner
46
+ return !this.properties.owner
47
+ }
48
+
49
+ isDraft() {
50
+ return this.properties.share_status === 0
51
+ }
52
+
53
+ userIsAuth() {
54
+ return Boolean(this._umap.properties.user?.id)
46
55
  }
47
56
 
48
57
  _editAnonymous(container) {
49
- const fields = []
50
58
  if (this.isOwner()) {
59
+ // We have a user, and this user has come through here, so they can edit the map, so let's allow to own the map.
60
+ // Note: real check is made on the back office anyway.
61
+ const template = `
62
+ <div class="anonymous soft-round aplat">
63
+ <h4><i class="icon icon-16 icon-anonymous"></i> ${translate('Anonymous map')}</h4>
64
+ <div data-ref="copiableInput"></div>
65
+ <p data-ref="p" hidden><button type="button" data-ref="button">${translate('Attach the map to my account')}</button></p>
66
+ </div>
67
+ `
68
+ const [root, { button, copiableInput, p }] = Utils.loadTemplateWithRefs(template)
69
+ container.appendChild(root)
70
+ if (this.properties.anonymous_edit_url) {
71
+ DOMUtils.copiableInput(
72
+ copiableInput,
73
+ translate('Secret edit link:'),
74
+ this.properties.anonymous_edit_url
75
+ )
76
+ }
77
+ if (this.userIsAuth()) {
78
+ button.addEventListener('click', () => this.attach())
79
+ p.hidden = false
80
+ }
81
+ const fields = []
51
82
  fields.push([
52
83
  'properties.edit_status',
53
84
  {
@@ -67,31 +98,6 @@ export class MapPermissions {
67
98
  const builder = new MutatingForm(this, fields)
68
99
  const form = builder.build()
69
100
  container.appendChild(form)
70
-
71
- if (this.properties.anonymous_edit_url) {
72
- DomUtil.createCopiableInput(
73
- container,
74
- translate('Secret edit link:'),
75
- this.properties.anonymous_edit_url
76
- )
77
- }
78
-
79
- if (this._umap.properties.user?.id) {
80
- // We have a user, and this user has come through here, so they can edit the map, so let's allow to own the map.
81
- // Note: real check is made on the back office anyway.
82
- const advancedActions = DomUtil.createFieldset(
83
- container,
84
- translate('Advanced actions')
85
- )
86
- const advancedButtons = DomUtil.create('div', 'button-bar', advancedActions)
87
- DomUtil.createButton(
88
- 'button',
89
- advancedButtons,
90
- translate('Attach the map to my account'),
91
- this.attach,
92
- this
93
- )
94
- }
95
101
  }
96
102
  }
97
103
 
@@ -187,6 +193,8 @@ export class MapPermissions {
187
193
  const [data, response, error] = await this._umap.server.post(this.getAttachUrl())
188
194
  if (!error) {
189
195
  this.properties.owner = this._umap.properties.user
196
+ this._umap.properties.user.is_owner = true
197
+ this.render()
190
198
  Alert.success(translate('Map has been attached to your account'))
191
199
  this._umap.editPanel.close()
192
200
  }
@@ -238,6 +246,10 @@ export class MapPermissions {
238
246
  )
239
247
  }
240
248
 
249
+ pull() {
250
+ this.setProperties(this._umap.properties.permissions)
251
+ }
252
+
241
253
  getShareStatusDisplay() {
242
254
  if (this._umap.properties.share_statuses) {
243
255
  return Object.fromEntries(this._umap.properties.share_statuses)[
@@ -245,10 +257,6 @@ export class MapPermissions {
245
257
  ]
246
258
  }
247
259
  }
248
-
249
- isDraft() {
250
- return this.properties.share_status === 0
251
- }
252
260
  }
253
261
 
254
262
  export class DataLayerPermissions {
@@ -112,7 +112,9 @@ export const PermanentCreditsControl = Control.extend({
112
112
  const container = Utils.loadTemplate(
113
113
  `<div class="umap-permanent-credits-container text">${Utils.toHTML(map.options.permanentCredit)}</div>`
114
114
  )
115
- const background = map.options.permanentCreditBackground ? '#FFFFFFB0' : ''
115
+ const background = map._umap.getProperty('permanentCreditBackground')
116
+ ? '#FFFFFFB0'
117
+ : ''
116
118
  container.style.backgroundColor = background
117
119
  return container
118
120
  },
@@ -164,7 +166,7 @@ export const DataLayersControl = BaseButton.extend({
164
166
  },
165
167
 
166
168
  afterAdd: function (container) {
167
- Utils.toggleBadge(container, this._umap.browser?.hasFilters())
169
+ Utils.toggleBadge(container, this._umap.browser?.hasActiveFilters())
168
170
  },
169
171
 
170
172
  onClick: function () {
@@ -177,7 +179,7 @@ export const CaptionControl = BaseButton.extend({
177
179
  position: 'topleft',
178
180
  className: 'umap-control-caption',
179
181
  title: translate('About'),
180
- icon: 'icon-caption',
182
+ icon: 'icon-info',
181
183
  },
182
184
 
183
185
  onClick: function () {
@@ -242,13 +244,13 @@ export const SearchControl = BaseButton.extend({
242
244
  const [container, { input, resultsContainer }] =
243
245
  Utils.loadTemplateWithRefs(template)
244
246
  const id = Math.random()
247
+ this.search = new U.Search(
248
+ this._umap._leafletMap,
249
+ input,
250
+ this.layer,
251
+ this.photonOptions
252
+ )
245
253
  this._umap.panel.open({ content: container }).then(() => {
246
- this.search = new U.Search(
247
- this._umap._leafletMap,
248
- input,
249
- this.layer,
250
- this.photonOptions
251
- )
252
254
  this.search.on('ajax:send', () => {
253
255
  this._umap.fire('dataloading', { id: id })
254
256
  })
@@ -1,12 +1,7 @@
1
- import {
2
- DivIcon,
3
- DomEvent,
4
- DomUtil,
5
- Icon,
6
- Point,
7
- } from '../../../vendors/leaflet/leaflet-src.esm.js'
1
+ import { DivIcon, Icon, Point } from '../../../vendors/leaflet/leaflet-src.esm.js'
8
2
  import { SCHEMA } from '../schema.js'
9
3
  import * as Utils from '../utils.js'
4
+ import * as DOMUtils from '../domutils.js'
10
5
 
11
6
  export function getClass(name) {
12
7
  switch (name) {
@@ -299,7 +294,7 @@ export function setContrast(icon, parent, src, bgcolor) {
299
294
  */
300
295
  if (!icon) return
301
296
 
302
- if (DomUtil.contrastedColor(parent, bgcolor)) {
297
+ if (DOMUtils.contrastedColor(parent, bgcolor)) {
303
298
  // Decide whether to switch svg to white or not, but do it
304
299
  // only for internal SVG, as invert could do weird things
305
300
  if (src.endsWith('.svg') && src !== SCHEMA.iconUrl.default) {
@@ -6,7 +6,7 @@ export const LayerMixin = {
6
6
  browsable: true,
7
7
 
8
8
  onInit: function (leafletMap) {
9
- if (this.datalayer.autoLoaded()) leafletMap.on('zoomend', this.onZoomEnd, this)
9
+ leafletMap.on('zoomend', this.onZoomEnd, this)
10
10
  },
11
11
 
12
12
  onDelete: function (leafletMap) {
@@ -48,12 +48,12 @@ export const LayerMixin = {
48
48
 
49
49
  onMoveEnd: function () {
50
50
  if (this.datalayer.hasDynamicData() && this.datalayer.showAtZoom()) {
51
- this.datalayer.fetchRemoteData()
51
+ this.datalayer.fetchData()
52
52
  }
53
53
  },
54
54
 
55
55
  onZoomEnd() {
56
- if (this.datalayer._forcedVisibility) return
56
+ if (!this.datalayer.autoVisibility) return
57
57
  if (!this.datalayer.showAtZoom() && this.datalayer.isVisible()) {
58
58
  this.datalayer.hide()
59
59
  }
@@ -2,11 +2,12 @@ import colorbrewer from '../../../../vendors/colorbrewer/colorbrewer.js'
2
2
  import { DomUtil, FeatureGroup } from '../../../../vendors/leaflet/leaflet-src.esm.js'
3
3
  import { translate } from '../../i18n.js'
4
4
  import * as Utils from '../../utils.js'
5
+ import * as DOMUtils from '../../domutils.js'
5
6
  import { CircleMarker } from '../ui.js'
6
7
  import { LayerMixin } from './base.js'
7
8
 
8
9
  // Layer where each feature color is relative to the others,
9
- // so we need all features before behing able to set one
10
+ // so we need all features before being able to set one
10
11
  // feature layer
11
12
  const ClassifiedMixin = {
12
13
  initialize: function (datalayer) {
@@ -76,15 +77,21 @@ const ClassifiedMixin = {
76
77
 
77
78
  renderLegend: function (container) {
78
79
  if (!this.datalayer.isLoaded()) return
79
- const parent = DomUtil.create('ul', '', container)
80
+ const ul = Utils.loadTemplate('<ul></ul>')
80
81
  const items = this.getLegendItems()
81
82
  for (const [color, label] of items) {
82
- const li = DomUtil.create('li', '', parent)
83
- const colorEl = DomUtil.create('span', 'datalayer-color', li)
84
- colorEl.style.backgroundColor = color
85
- const labelEl = DomUtil.create('span', '', li)
86
- labelEl.textContent = label
83
+ const rgbColor = DOMUtils.hexToRGB(color)
84
+ const opacity = this.datalayer.getOption('fillOpacity')
85
+ const bgColor = `rgba(${rgbColor.join(',')}, ${opacity})`
86
+ const li = Utils.loadTemplate(`
87
+ <li>
88
+ <span class="datalayer-color" style="background-color: ${bgColor};"></span>
89
+ <span>${label}</span>
90
+ </li>
91
+ `)
92
+ ul.appendChild(li)
87
93
  }
94
+ container.appendChild(ul)
88
95
  },
89
96
 
90
97
  getColorSchemes: function (classes) {
@@ -198,7 +205,7 @@ export const Choropleth = FeatureGroup.extend({
198
205
  'properties.choropleth.property',
199
206
  {
200
207
  handler: 'Select',
201
- selectOptions: this.datalayer.fieldKeys,
208
+ selectOptions: this.datalayer.fields.keys(),
202
209
  label: translate('Choropleth property value'),
203
210
  },
204
211
  ],
@@ -307,7 +314,7 @@ export const Circles = FeatureGroup.extend({
307
314
  'properties.circles.property',
308
315
  {
309
316
  handler: 'Select',
310
- selectOptions: this.datalayer.fieldKeys,
317
+ selectOptions: this.datalayer.fields.keys(),
311
318
  label: translate('Property name to compute circles'),
312
319
  },
313
320
  ],
@@ -384,7 +391,7 @@ export const Categorized = FeatureGroup.extend({
384
391
 
385
392
  _getValue: function (feature) {
386
393
  const key =
387
- this.datalayer.properties.categorized.property || this.datalayer.fieldKeys[0]
394
+ this.datalayer.properties.categorized.property || this.datalayer.fields.keys()[0]
388
395
  return feature.properties[key]
389
396
  },
390
397
 
@@ -437,7 +444,7 @@ export const Categorized = FeatureGroup.extend({
437
444
  'properties.categorized.property',
438
445
  {
439
446
  handler: 'Select',
440
- selectOptions: this.datalayer.fieldKeys,
447
+ selectOptions: this.datalayer.fields.keys(),
441
448
  label: translate('Category property'),
442
449
  },
443
450
  ],
@@ -5,11 +5,11 @@ import {
5
5
  Marker,
6
6
  Rectangle,
7
7
  Polyline,
8
- DomUtil,
9
8
  latLngBounds,
10
9
  } from '../../../../vendors/leaflet/leaflet-src.esm.js'
11
10
  import { translate } from '../../i18n.js'
12
11
  import * as Utils from '../../utils.js'
12
+ import * as DOMUtils from '../../domutils.js'
13
13
  import { Cluster as ClusterIcon } from '../icon.js'
14
14
  import { LayerMixin } from './base.js'
15
15
 
@@ -21,7 +21,7 @@ const MarkerCluster = Marker.extend({
21
21
  const bgColor = this.options.icon.options.color
22
22
  const textColor = this.options.icon.options.textColor
23
23
  counter.style.color =
24
- textColor || DomUtil.TextColorFromBackgroundColor(counter, bgColor)
24
+ textColor || DOMUtils.textColorFromBackgroundColor(counter, bgColor)
25
25
  },
26
26
 
27
27
  computeCoverage() {
@@ -186,22 +186,24 @@ export const Cluster = FeatureGroup.extend({
186
186
  return this
187
187
  },
188
188
 
189
+ removeLayer: function (layer) {
190
+ if (!layer.getLatLng) return FeatureGroup.prototype.removeLayer.call(this, layer)
191
+ this._bucket = this._bucket.filter((el) => el !== layer)
192
+ return this
193
+ },
194
+
189
195
  onAdd: function (leafletMap) {
190
196
  this.on('click', this.onClick)
191
197
  this.on('mouseover', this.onMouseOver)
192
198
  this.on('mouseout', this.onMouseOut)
193
199
  this.compute()
194
200
  LayerMixin.onAdd.call(this, leafletMap)
195
- leafletMap.on('moveend', this.onMoveEnd, this)
196
- leafletMap.on('zoomend', this.onZoomEnd, this)
197
201
  this.addClusters()
198
202
  leafletMap.addLayer(this._group)
199
203
  return FeatureGroup.prototype.onAdd.call(this, leafletMap)
200
204
  },
201
205
 
202
206
  onRemove: function (leafletMap) {
203
- leafletMap.off('zoomend', this.onZoomEnd, this)
204
- leafletMap.off('moveend', this.onMoveEnd, this)
205
207
  this.off('click', this.onClick)
206
208
  this.off('mouseover', this.onMouseOver)
207
209
  this.off('mouseout', this.onMouseOut)
@@ -212,12 +214,18 @@ export const Cluster = FeatureGroup.extend({
212
214
  },
213
215
 
214
216
  onZoomEnd: function () {
217
+ LayerMixin.onZoomEnd.call(this)
215
218
  this.removeClusters()
216
219
  },
217
220
 
218
221
  onMoveEnd: function () {
219
- this.compute()
220
- this.addClusters()
222
+ LayerMixin.onMoveEnd.call(this)
223
+ // In case of dynamic data, the LayerMixin.onMoveEnd
224
+ // call with fetch the data and then call the compute
225
+ if (!this.datalayer.hasDynamicData()) {
226
+ this.compute()
227
+ this.addClusters()
228
+ }
221
229
  },
222
230
 
223
231
  showCoverage(cluster) {
@@ -228,7 +236,9 @@ export const Cluster = FeatureGroup.extend({
228
236
  },
229
237
 
230
238
  hideCoverage() {
231
- if (this._shownCoverage) this._map.removeLayer(this._shownCoverage)
239
+ if (this._shownCoverage && this._map) {
240
+ this._map.removeLayer(this._shownCoverage)
241
+ }
232
242
  },
233
243
 
234
244
  onMouseOver(event) {
@@ -253,7 +263,7 @@ export const Cluster = FeatureGroup.extend({
253
263
  'properties.cluster.radius',
254
264
  {
255
265
  handler: 'Range',
256
- min: 40,
266
+ min: 10,
257
267
  max: 200,
258
268
  step: 10,
259
269
  placeholder: translate('Clustering radius'),
@@ -271,6 +281,8 @@ export const Cluster = FeatureGroup.extend({
271
281
  ],
272
282
 
273
283
  onEdit: function (field, builder) {
274
- if (field === 'properties.cluster.radius') this.redraw()
284
+ if (field === 'properties.cluster.radius' || field === 'properties.color') {
285
+ this.redraw()
286
+ }
275
287
  },
276
288
  })
@@ -68,27 +68,32 @@ export const Heat = L.HeatLayer.extend({
68
68
  return latLngBounds(this._latlngs)
69
69
  },
70
70
 
71
- getEditableProperties: () => [
72
- [
73
- 'properties.heat.radius',
74
- {
75
- handler: 'Range',
76
- min: 10,
77
- max: 100,
78
- step: 5,
79
- label: translate('Heatmap radius'),
80
- helpText: translate('Override heatmap radius (default 25)'),
81
- },
82
- ],
83
- [
84
- 'properties.heat.intensityProperty',
85
- {
86
- handler: 'BlurInput',
87
- placeholder: translate('Heatmap intensity property'),
88
- helpText: translate('Optional intensity property for heatmap'),
89
- },
90
- ],
91
- ],
71
+ getEditableProperties: function () {
72
+ return [
73
+ [
74
+ 'properties.heat.radius',
75
+ {
76
+ handler: 'Range',
77
+ min: 10,
78
+ max: 100,
79
+ step: 5,
80
+ label: translate('Heatmap radius'),
81
+ helpText: translate('Override heatmap radius (default 25)'),
82
+ },
83
+ ],
84
+ [
85
+ 'properties.heat.intensityProperty',
86
+ {
87
+ handler: 'Select',
88
+ selectOptions: [
89
+ ['', translate('Select field to compute intensity')],
90
+ ...this.datalayer.fields.keys(),
91
+ ],
92
+ helpText: translate('Optional intensity field to compute heatmap'),
93
+ },
94
+ ],
95
+ ]
96
+ },
92
97
 
93
98
  onEdit: function (field, builder) {
94
99
  if (field === 'properties.heat.intensityProperty') {
@@ -97,6 +102,7 @@ export const Heat = L.HeatLayer.extend({
97
102
  }
98
103
  if (field === 'properties.heat.radius') {
99
104
  this.options.radius = this.datalayer.properties.heat.radius
105
+ this.redraw()
100
106
  }
101
107
  this._updateOptions()
102
108
  },
@@ -154,6 +154,7 @@ const ControlsMixin = {
154
154
 
155
155
  const ManageTilelayerMixin = {
156
156
  initTileLayers: function () {
157
+ this.pullProperties()
157
158
  this.tilelayers = []
158
159
  for (const props of this.options.tilelayers) {
159
160
  const layer = this.createTileLayer(props)
@@ -6,6 +6,7 @@ import {
6
6
  import { getLocale, translate } from '../i18n.js'
7
7
  import { Request } from '../request.js'
8
8
  import * as Utils from '../utils.js'
9
+ import * as DOMUtils from '../domutils.js'
9
10
  import * as Icon from './icon.js'
10
11
 
11
12
  export default async function loadTemplate(name, feature, container) {
@@ -107,29 +108,20 @@ export const TitleMixin = (Base) =>
107
108
  }
108
109
 
109
110
  class Table extends TitleMixin(PopupTemplate) {
110
- getValue(feature, key) {
111
- // TODO, manage links (url, mailto, wikipedia...)
112
- const value = Utils.escapeHTML(feature.properties[key]).trim()
113
- if (value.indexOf('http') === 0) {
114
- return `<a href="${value}" target="_blank">${value}</a>`
115
- }
116
- return value
117
- }
118
-
119
- makeRow(feature, key) {
111
+ makeRow(feature, field) {
120
112
  return Utils.loadTemplate(
121
- `<tr><th>${key}</th><td>${this.getValue(feature, key)}</td></tr>`
113
+ `<tr><th>${field.key}</th><td>${field.render(feature.properties[field.key])}</td></tr>`
122
114
  )
123
115
  }
124
116
 
125
117
  async renderBody(feature) {
126
118
  const table = document.createElement('table')
127
119
 
128
- for (const key in feature.properties) {
129
- if (typeof feature.properties[key] === 'object' || U.LABEL_KEYS.includes(key)) {
120
+ for (const field of feature.fields) {
121
+ if (U.LABEL_KEYS.includes(field.key)) {
130
122
  continue
131
123
  }
132
- table.appendChild(this.makeRow(feature, key))
124
+ table.appendChild(this.makeRow(feature, field))
133
125
  }
134
126
  return table
135
127
  }
@@ -171,7 +163,7 @@ class OSM extends PopupTemplate {
171
163
  const icon = Icon.makeElement(iconUrl, title)
172
164
  DomUtil.addClass(icon, 'icon')
173
165
  Icon.setContrast(icon, title, iconUrl, color)
174
- if (DomUtil.contrastedColor(title, color)) title.style.color = 'white'
166
+ if (DOMUtils.contrastedColor(title, color)) title.style.color = 'white'
175
167
  DomUtil.add('span', '', title, this.getName(feature))
176
168
  return title
177
169
  }
@@ -180,7 +172,7 @@ class OSM extends PopupTemplate {
180
172
  const props = feature.properties
181
173
  const locale = getLocale()
182
174
  if (locale && props[`name:${locale}`]) return props[`name:${locale}`]
183
- return props.name
175
+ return props.name || feature.getDisplayName()
184
176
  }
185
177
 
186
178
  async renderBody(feature) {
@@ -192,7 +184,7 @@ class OSM extends PopupTemplate {
192
184
  const row = DomUtil.add('address', 'address', body)
193
185
  const number = props['addr:housenumber']
194
186
  if (number) {
195
- // Poor way to deal with international forms of writting addresses
187
+ // Poor way to deal with international forms of writing addresses
196
188
  DomUtil.add('span', '', row, `${translate('No.')}: ${number}`)
197
189
  DomUtil.add('span', '', row, `${translate('Street')}: ${street}`)
198
190
  } else {
@@ -230,11 +222,31 @@ class OSM extends PopupTemplate {
230
222
  )
231
223
  )
232
224
  }
225
+ if (props.image) {
226
+ body.appendChild(
227
+ Utils.loadTemplate(`<div><img src="${props.image}" alt="" /></div>`)
228
+ )
229
+ }
230
+ if (props.mapillary) {
231
+ body.appendChild(
232
+ Utils.loadTemplate(
233
+ `<div><a href="https://www.mapillary.com/app/?focus=photo&pKey=${props.mapillary}" target="_blank">${translate('Mapillary')}<i class="icon icon-16 icon-external-link"></i></a></div>`
234
+ )
235
+ )
236
+ }
233
237
  const wikipedia = props[`wikipedia:${locale}`] || props.wikipedia
234
238
  if (wikipedia) {
235
239
  body.appendChild(
236
240
  Utils.loadTemplate(
237
- `<div class="wikipedia-link"><a href="https://wikipedia.org/wiki/${wikipedia}" target="_blank">${translate('Wikipedia')}</a></div>`
241
+ `<div><a href="https://wikipedia.org/wiki/${wikipedia}" target="_blank">${translate('Wikipedia')}<i class="icon icon-16 icon-external-link"></i></a></div>`
242
+ )
243
+ )
244
+ }
245
+ const wikidata = props[`wikidata:${locale}`] || props.wikidata
246
+ if (wikidata) {
247
+ body.appendChild(
248
+ Utils.loadTemplate(
249
+ `<div><a href="https://www.wikidata.org/wiki/${wikidata}" target="_blank">${translate('Wikidata')}<i class="icon icon-16 icon-external-link"></i></a></div>`
238
250
  )
239
251
  )
240
252
  }
@@ -242,7 +254,7 @@ class OSM extends PopupTemplate {
242
254
  if (id) {
243
255
  body.appendChild(
244
256
  Utils.loadTemplate(
245
- `<div class="osm-link"><a href="https://www.openstreetmap.org/${id}">${translate('See on OpenStreetMap')}</a></div>`
257
+ `<div class="osm-link"><a href="https://www.openstreetmap.org/${id}">${translate('See on OpenStreetMap')}<i class="icon icon-16 icon-external-link"></i></a></div>`
246
258
  )
247
259
  )
248
260
  }
@@ -257,7 +269,7 @@ class Wikipedia extends PopupTemplate {
257
269
  if (wikipedia && _WIKIPEDIA_CACHE[wikipedia]) return _WIKIPEDIA_CACHE[wikipedia]
258
270
  // Wikipedia value should be in form of "{locale}:{title}", according to https://wiki.openstreetmap.org/wiki/Key:wikipedia
259
271
  const [locale, page] = wikipedia.split(':')
260
- const url = `https://${locale}.wikipedia.org/w/api.php?action=query&format=json&origin=*&pithumbsize=500&prop=extracts|pageimages&titles=${page}`
272
+ const url = `https://${locale}.wikipedia.org/w/api.php?action=query&format=json&origin=*&pithumbsize=500&exintro=1&prop=extracts|pageimages&titles=${page}`
261
273
  const request = new Request()
262
274
  const response = await request.get(url)
263
275
  if (response?.ok) {
@@ -277,9 +289,21 @@ class Wikipedia extends PopupTemplate {
277
289
  const title = page.title || feature.getDisplayName()
278
290
  const extract = page.extract || ''
279
291
  const thumbnail = page.thumbnail?.source
280
- const [content, { image }] = Utils.loadTemplateWithRefs(
281
- `<div><h3>${Utils.escapeHTML(title)}</h3><img data-ref="image" hidden src="" />${Utils.escapeHTML(extract)}</div>`
282
- )
292
+ const [content, { image }] = Utils.loadTemplateWithRefs(`
293
+ <div>
294
+ <h3>${Utils.escapeHTML(title)}</h3>
295
+ <img data-ref="image" hidden src="" />
296
+ <p>
297
+ ${Utils.escapeHTML(extract)}
298
+ </p>
299
+ <p>
300
+ © ${translate('Wikipedia contributors')} •
301
+ <a href="https://wikipedia.org/wiki/${wikipedia}" target="_blank">
302
+ ${translate('See on Wikipedia')}
303
+ <i class="icon icon-16 icon-external-link"></i>
304
+ </a>
305
+ </p>
306
+ </div>`)
283
307
  if (thumbnail) {
284
308
  image.src = thumbnail
285
309
  image.hidden = false
@@ -349,6 +373,9 @@ class Route extends TitleMixin(PopupTemplate) {
349
373
  color: 'orange',
350
374
  }).addTo(map)
351
375
  })
376
+ if (feature.properties.description) {
377
+ root.appendChild(Utils.loadTemplate(`<p>${feature.properties.description}</p>`))
378
+ }
352
379
  return root
353
380
  }
354
381
  }