umap-project 3.3.6__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 (239) 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 +35 -29
  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 +85 -60
  65. umap/static/umap/js/modules/data/fields.js +446 -0
  66. umap/static/umap/js/modules/data/layer.js +78 -184
  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 -2
  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 +1 -1
  82. umap/static/umap/js/modules/rendering/layers/classified.js +18 -11
  83. umap/static/umap/js/modules/rendering/layers/cluster.js +5 -3
  84. umap/static/umap/js/modules/rendering/layers/heat.js +27 -21
  85. umap/static/umap/js/modules/rendering/template.js +50 -23
  86. umap/static/umap/js/modules/rendering/ui.js +29 -23
  87. umap/static/umap/js/modules/rules.js +38 -44
  88. umap/static/umap/js/modules/schema.js +3 -6
  89. umap/static/umap/js/modules/share.js +5 -4
  90. umap/static/umap/js/modules/tableeditor.js +50 -38
  91. umap/static/umap/js/modules/templates.js +2 -3
  92. umap/static/umap/js/modules/ui/bar.js +55 -23
  93. umap/static/umap/js/modules/ui/dialog.js +38 -27
  94. umap/static/umap/js/modules/ui/panel.js +23 -8
  95. umap/static/umap/js/modules/ui/tooltip.js +6 -5
  96. umap/static/umap/js/modules/umap.js +151 -56
  97. umap/static/umap/js/modules/utils.js +24 -2
  98. umap/static/umap/js/umap.core.js +1 -110
  99. umap/static/umap/locale/am_ET.js +52 -17
  100. umap/static/umap/locale/am_ET.json +52 -17
  101. umap/static/umap/locale/ar.js +52 -17
  102. umap/static/umap/locale/ar.json +52 -17
  103. umap/static/umap/locale/ast.js +52 -17
  104. umap/static/umap/locale/ast.json +52 -17
  105. umap/static/umap/locale/bg.js +52 -17
  106. umap/static/umap/locale/bg.json +52 -17
  107. umap/static/umap/locale/br.js +48 -22
  108. umap/static/umap/locale/br.json +48 -22
  109. umap/static/umap/locale/ca.js +52 -17
  110. umap/static/umap/locale/ca.json +52 -17
  111. umap/static/umap/locale/cs_CZ.js +52 -17
  112. umap/static/umap/locale/cs_CZ.json +52 -17
  113. umap/static/umap/locale/da.js +54 -17
  114. umap/static/umap/locale/da.json +54 -17
  115. umap/static/umap/locale/de.js +51 -16
  116. umap/static/umap/locale/de.json +51 -16
  117. umap/static/umap/locale/el.js +52 -17
  118. umap/static/umap/locale/el.json +52 -17
  119. umap/static/umap/locale/en.js +53 -16
  120. umap/static/umap/locale/en.json +53 -16
  121. umap/static/umap/locale/en_US.json +52 -17
  122. umap/static/umap/locale/es.js +54 -17
  123. umap/static/umap/locale/es.json +54 -17
  124. umap/static/umap/locale/et.js +91 -56
  125. umap/static/umap/locale/et.json +91 -56
  126. umap/static/umap/locale/eu.js +84 -49
  127. umap/static/umap/locale/eu.json +84 -49
  128. umap/static/umap/locale/fa_IR.js +52 -17
  129. umap/static/umap/locale/fa_IR.json +52 -17
  130. umap/static/umap/locale/fi.js +52 -17
  131. umap/static/umap/locale/fi.json +52 -17
  132. umap/static/umap/locale/fr.js +53 -16
  133. umap/static/umap/locale/fr.json +53 -16
  134. umap/static/umap/locale/gl.js +52 -17
  135. umap/static/umap/locale/gl.json +52 -17
  136. umap/static/umap/locale/he.js +52 -17
  137. umap/static/umap/locale/he.json +52 -17
  138. umap/static/umap/locale/hr.js +52 -17
  139. umap/static/umap/locale/hr.json +52 -17
  140. umap/static/umap/locale/hu.js +59 -24
  141. umap/static/umap/locale/hu.json +59 -24
  142. umap/static/umap/locale/id.js +52 -17
  143. umap/static/umap/locale/id.json +52 -17
  144. umap/static/umap/locale/is.js +52 -17
  145. umap/static/umap/locale/is.json +52 -17
  146. umap/static/umap/locale/it.js +52 -17
  147. umap/static/umap/locale/it.json +52 -17
  148. umap/static/umap/locale/ja.js +52 -17
  149. umap/static/umap/locale/ja.json +52 -17
  150. umap/static/umap/locale/ko.js +52 -17
  151. umap/static/umap/locale/ko.json +52 -17
  152. umap/static/umap/locale/lt.js +52 -17
  153. umap/static/umap/locale/lt.json +52 -17
  154. umap/static/umap/locale/ms.js +52 -17
  155. umap/static/umap/locale/ms.json +52 -17
  156. umap/static/umap/locale/nl.js +52 -17
  157. umap/static/umap/locale/nl.json +52 -17
  158. umap/static/umap/locale/no.js +52 -17
  159. umap/static/umap/locale/no.json +52 -17
  160. umap/static/umap/locale/pl.js +53 -17
  161. umap/static/umap/locale/pl.json +53 -17
  162. umap/static/umap/locale/pl_PL.json +52 -17
  163. umap/static/umap/locale/pt.js +52 -17
  164. umap/static/umap/locale/pt.json +52 -17
  165. umap/static/umap/locale/pt_BR.js +52 -17
  166. umap/static/umap/locale/pt_BR.json +52 -17
  167. umap/static/umap/locale/pt_PT.js +52 -17
  168. umap/static/umap/locale/pt_PT.json +52 -17
  169. umap/static/umap/locale/ro.js +52 -17
  170. umap/static/umap/locale/ro.json +52 -17
  171. umap/static/umap/locale/ru.js +52 -17
  172. umap/static/umap/locale/ru.json +52 -17
  173. umap/static/umap/locale/si.js +1 -1
  174. umap/static/umap/locale/si.json +1 -1
  175. umap/static/umap/locale/sk_SK.js +52 -17
  176. umap/static/umap/locale/sk_SK.json +52 -17
  177. umap/static/umap/locale/sl.js +52 -17
  178. umap/static/umap/locale/sl.json +52 -17
  179. umap/static/umap/locale/sr.js +52 -17
  180. umap/static/umap/locale/sr.json +52 -17
  181. umap/static/umap/locale/sv.js +52 -17
  182. umap/static/umap/locale/sv.json +52 -17
  183. umap/static/umap/locale/th_TH.js +52 -17
  184. umap/static/umap/locale/th_TH.json +52 -17
  185. umap/static/umap/locale/tr.js +52 -17
  186. umap/static/umap/locale/tr.json +52 -17
  187. umap/static/umap/locale/uk_UA.js +52 -17
  188. umap/static/umap/locale/uk_UA.json +52 -17
  189. umap/static/umap/locale/vi.js +52 -17
  190. umap/static/umap/locale/vi.json +52 -17
  191. umap/static/umap/locale/vi_VN.json +52 -17
  192. umap/static/umap/locale/zh.js +52 -17
  193. umap/static/umap/locale/zh.json +52 -17
  194. umap/static/umap/locale/zh_CN.json +52 -17
  195. umap/static/umap/locale/zh_TW.Big5.json +52 -17
  196. umap/static/umap/locale/zh_TW.js +52 -16
  197. umap/static/umap/locale/zh_TW.json +52 -16
  198. umap/static/umap/map.css +63 -226
  199. umap/static/umap/unittests/utils.js +18 -0
  200. umap/static/umap/vars.css +23 -5
  201. umap/templates/umap/components/alerts/alert.html +32 -29
  202. umap/templates/umap/css.html +2 -1
  203. umap/templates/umap/login_popup_end.html +18 -9
  204. umap/templates/umap/user_map_table.html +7 -2
  205. umap/tests/integration/conftest.py +10 -6
  206. umap/tests/integration/test_anonymous_owned_map.py +90 -37
  207. umap/tests/integration/test_basics.py +25 -1
  208. umap/tests/integration/test_browser.py +37 -0
  209. umap/tests/integration/test_conditional_rules.py +107 -52
  210. umap/tests/integration/test_draw_polygon.py +6 -0
  211. umap/tests/integration/test_draw_polyline.py +11 -0
  212. umap/tests/integration/test_edit_marker.py +1 -1
  213. umap/tests/integration/test_export_map.py +19 -0
  214. umap/tests/integration/test_fields.py +541 -0
  215. umap/tests/integration/test_filters.py +616 -0
  216. umap/tests/integration/test_iframe.py +1 -1
  217. umap/tests/integration/test_import.py +38 -42
  218. umap/tests/integration/test_map_preview.py +1 -1
  219. umap/tests/integration/test_picto.py +1 -1
  220. umap/tests/integration/test_popup.py +31 -0
  221. umap/tests/integration/test_remote_data.py +60 -4
  222. umap/tests/integration/test_save.py +1 -1
  223. umap/tests/integration/test_share.py +4 -4
  224. umap/tests/integration/test_tableeditor.py +31 -7
  225. umap/tests/integration/test_websocket_sync.py +71 -20
  226. umap/tests/test_dashboard.py +11 -1
  227. umap/tests/test_statics.py +2 -2
  228. umap/tests/test_utils.py +19 -2
  229. umap/tests/test_views.py +1 -1
  230. umap/urls.py +1 -0
  231. umap/utils.py +8 -1
  232. umap/views.py +5 -0
  233. {umap_project-3.3.6.dist-info → umap_project-3.4.0.dist-info}/METADATA +15 -15
  234. {umap_project-3.3.6.dist-info → umap_project-3.4.0.dist-info}/RECORD +237 -233
  235. umap/static/umap/js/modules/facets.js +0 -164
  236. umap/tests/integration/test_facets_browser.py +0 -279
  237. {umap_project-3.3.6.dist-info → umap_project-3.4.0.dist-info}/WHEEL +0 -0
  238. {umap_project-3.3.6.dist-info → umap_project-3.4.0.dist-info}/entry_points.txt +0 -0
  239. {umap_project-3.3.6.dist-info → umap_project-3.4.0.dist-info}/licenses/LICENSE +0 -0
@@ -7,7 +7,7 @@ export class Panel {
7
7
  this._umap = umap
8
8
  this._leafletMap = leafletMap
9
9
  this.container = DomUtil.create('div', '', this.parent)
10
- // This will be set once according to the panel configurated at load
10
+ // This will be set once according to the panel configured at load
11
11
  // or by using panels as popups
12
12
  this.mode = null
13
13
  this.className = 'left'
@@ -26,12 +26,12 @@ export class Panel {
26
26
  }
27
27
 
28
28
  open({ content, className, highlight, actions = [] } = {}) {
29
+ let isOpen = false
29
30
  if (this.isOpen()) {
31
+ isOpen = true
30
32
  this.onClose()
31
33
  }
32
- this.container.className = `with-transition panel window ${this.className} ${
33
- this.mode || ''
34
- }`
34
+ this.container.className = `with-transition panel window ${this.className} ${this.mode || ''} ${isOpen ? 'on' : ''}`
35
35
  if (highlight) {
36
36
  this.container.dataset.highlight = highlight
37
37
  }
@@ -56,8 +56,22 @@ export class Panel {
56
56
  }
57
57
  if (className) DomUtil.addClass(body, className)
58
58
  const promise = new Promise((resolve, reject) => {
59
- DomUtil.addClass(this.container, 'on')
60
- resolve(this)
59
+ if (isOpen) {
60
+ resolve(this)
61
+ } else {
62
+ this.container.classList.add('on')
63
+ Promise.all(
64
+ this.container.getAnimations?.().map((animation) => animation.finished)
65
+ )
66
+ .then(() => {
67
+ resolve(this)
68
+ })
69
+ .catch(() => {
70
+ // Panel has been removed, so the DOM has changed, so the animations
71
+ // were cancelled, we want the new panel callback to be called anyway.
72
+ resolve(this)
73
+ })
74
+ }
61
75
  })
62
76
  DomEvent.on(closeButton, 'click', this.close, this)
63
77
  DomEvent.on(resizeButton, 'click', this.resize, this)
@@ -83,11 +97,12 @@ export class Panel {
83
97
  }
84
98
 
85
99
  onClose() {
86
- if (DomUtil.hasClass(this.container, 'on')) {
87
- DomUtil.removeClass(this.container, 'on')
100
+ if (this.container.classList.contains('on')) {
101
+ this.container.classList.remove('on')
88
102
  this._leafletMap.invalidateSize({ pan: false })
89
103
  }
90
104
  }
105
+
91
106
  scrollTo(selector) {
92
107
  const fieldset = this.container.querySelector(selector)
93
108
  if (!fieldset) return
@@ -4,11 +4,10 @@ import * as Utils from '../utils.js'
4
4
  import { Positioned } from './base.js'
5
5
 
6
6
  export default class Tooltip extends Positioned {
7
- constructor(parent) {
7
+ constructor() {
8
8
  super()
9
- this.parent = parent
9
+ this.parent = document.body
10
10
  this.container = Utils.loadTemplate('<div class="umap-tooltip-container"></div>')
11
- this.parent.appendChild(this.container)
12
11
  DomEvent.disableClickPropagation(this.container)
13
12
  this.container.addEventListener('contextmenu', (event) => event.stopPropagation()) // Do not activate our custom context menu.
14
13
  this.container.addEventListener('wheel', (event) => event.stopPropagation())
@@ -25,7 +24,7 @@ export default class Tooltip extends Positioned {
25
24
  } else {
26
25
  this.container.innerHTML = Utils.escapeHTML(opts.content)
27
26
  }
28
- this.parent.classList.add('umap-tooltip')
27
+ this.parent.appendChild(this.container)
29
28
  this.openAt(opts)
30
29
  }
31
30
  this.TOOLTIP_ID = window.setTimeout(L.bind(showIt, this), opts.delay || 0)
@@ -49,6 +48,8 @@ export default class Tooltip extends Positioned {
49
48
  this.toggleClassPosition()
50
49
  this.container.innerHTML = ''
51
50
  this.setPosition({})
52
- this.parent.classList.remove('umap-tooltip')
51
+ if (this.parent.contains(this.container)) {
52
+ this.parent.removeChild(this.container)
53
+ }
53
54
  }
54
55
  }
@@ -10,7 +10,8 @@ import {
10
10
  import Browser from './browser.js'
11
11
  import Caption from './caption.js'
12
12
  import { DataLayer } from './data/layer.js'
13
- import Facets from './facets.js'
13
+ import { Fields } from './data/fields.js'
14
+ import { Filters, migrateLegacyFilters } from './filters.js'
14
15
  import { MutatingForm } from './form/builder.js'
15
16
  import { Formatter } from './formatter.js'
16
17
  import Help from './help.js'
@@ -32,6 +33,7 @@ import { EditPanel, FullPanel, Panel } from './ui/panel.js'
32
33
  import Tooltip from './ui/tooltip.js'
33
34
  import URLs from './urls.js'
34
35
  import * as Utils from './utils.js'
36
+ import * as DOMUtils from './domutils.js'
35
37
  import { DataLayerManager } from './managers.js'
36
38
  import { Importer as OpenRouteService } from './importers/openrouteservice.js'
37
39
 
@@ -48,6 +50,7 @@ export default class Umap {
48
50
  }
49
51
 
50
52
  async init(element, geojson) {
53
+ this.migrateLegacyProperties(geojson.properties)
51
54
  this.properties = Object.assign(
52
55
  {
53
56
  enableMarkerDraw: true,
@@ -131,7 +134,8 @@ export default class Umap {
131
134
  this.contextmenu = new ContextMenu()
132
135
  this.server = new ServerRequest()
133
136
  this.request = new Request()
134
- this.facets = new Facets(this)
137
+ this.fields = new Fields(this, this.dialog)
138
+ this.filters = new Filters(this, this)
135
139
  this.browser = new Browser(this, this._leafletMap)
136
140
  this.caption = new Caption(this, this._leafletMap)
137
141
  this.importer = new Importer(this)
@@ -160,10 +164,6 @@ export default class Umap {
160
164
  ) {
161
165
  this.properties.slideshow.active = true
162
166
  }
163
- if (this.properties.advancedFilterKey) {
164
- this.properties.facetKey = this.properties.advancedFilterKey
165
- delete this.properties.advancedFilterKey
166
- }
167
167
 
168
168
  // Global storage for retrieving datalayers and features.
169
169
  this.datalayers = new DataLayerManager()
@@ -261,17 +261,6 @@ export default class Umap {
261
261
  return window.self !== window.top
262
262
  }
263
263
 
264
- get fields() {
265
- if (!this.properties.fields) this.properties.fields = []
266
- return this.properties.fields
267
- }
268
-
269
- get fieldKeys() {
270
- return this.fields
271
- .map((field) => field.key)
272
- .concat(...this.datalayers.active().map((dl) => dl.fieldKeys))
273
- }
274
-
275
264
  setPropertiesFromQueryString() {
276
265
  const asBoolean = (key) => {
277
266
  const value = this.searchParams.get(key)
@@ -391,6 +380,17 @@ export default class Umap {
391
380
  }
392
381
  }
393
382
 
383
+ hasFilters() {
384
+ return this.filters.size || this.datalayers.active().some((d) => d.filters.size)
385
+ }
386
+
387
+ hasActiveFilters() {
388
+ return (
389
+ this.filters.isActive() ||
390
+ this.datalayers.active().some((d) => d.filters.isActive())
391
+ )
392
+ }
393
+
394
394
  getOwnContextMenu(event) {
395
395
  const items = []
396
396
  if (this.editEnabled) {
@@ -424,7 +424,7 @@ export default class Umap {
424
424
  action: () => this.openBrowser('data'),
425
425
  }
426
426
  )
427
- if (this.properties.facetKey) {
427
+ if (this.hasFilters()) {
428
428
  items.push({
429
429
  label: translate('Filter data'),
430
430
  action: () => this.openBrowser('filters'),
@@ -444,7 +444,13 @@ export default class Umap {
444
444
  }
445
445
 
446
446
  getSharedContextMenu(event) {
447
- const items = []
447
+ const latlng = `${event.latlng.lat.toFixed(6)},${event.latlng.lng.toFixed(6)}`
448
+ const items = [
449
+ {
450
+ label: latlng,
451
+ action: () => DOMUtils.copyToClipboard(latlng),
452
+ },
453
+ ]
448
454
  if (this.properties.urls.routing) {
449
455
  items.push({
450
456
  label: translate('Directions from here'),
@@ -481,7 +487,7 @@ export default class Umap {
481
487
  }
482
488
 
483
489
  search() {
484
- if (this._leafletMap._controls.search) this._leafletMap._controls.search.open()
490
+ if (this._leafletMap._controls.search) this._leafletMap._controls.search.onClick()
485
491
  }
486
492
 
487
493
  hasEditMode() {
@@ -666,7 +672,7 @@ export default class Umap {
666
672
  const parent = this._leafletMap.getPane('overlayPane')
667
673
  const datalayers = Object.values(this.datalayers)
668
674
  .filter((datalayer) => !datalayer._isDeleted)
669
- .sort((datalayer1, datalayer2) => datalayer1.rank > datalayer2.rank)
675
+ .sort((datalayer1, datalayer2) => datalayer1.rank - datalayer2.rank)
670
676
  for (const datalayer of datalayers) {
671
677
  const child = parent.querySelector(`[data-id="${datalayer.id}"]`)
672
678
  parent.appendChild(child)
@@ -720,7 +726,14 @@ export default class Umap {
720
726
  return this.properties.name || translate('Untitled map')
721
727
  }
722
728
 
729
+ migrateLegacyProperties(properties) {
730
+ if (migrateLegacyFilters(properties)) {
731
+ this._migrated = true
732
+ }
733
+ }
734
+
723
735
  setProperties(newProperties) {
736
+ this.migrateLegacyProperties(newProperties)
724
737
  for (const key of Object.keys(SCHEMA)) {
725
738
  if (newProperties[key] !== undefined) {
726
739
  this.properties[key] = newProperties[key]
@@ -758,7 +771,7 @@ export default class Umap {
758
771
  'properties.is_template',
759
772
  ]
760
773
 
761
- DomUtil.createTitle(container, translate('Edit map details'), 'icon-caption')
774
+ DomUtil.createTitle(container, translate('Edit map details'), 'icon-info')
762
775
  const builder = new MutatingForm(this, metadataFields, {
763
776
  className: 'map-metadata',
764
777
  umap: this,
@@ -868,23 +881,22 @@ export default class Umap {
868
881
  defaultShapeProperties.appendChild(builder.build())
869
882
  }
870
883
 
871
- _editDefaultProperties(container) {
872
- const optionsFields = [
884
+ _editDefaultKeys(container) {
885
+ const shapeOptions = [
873
886
  'properties.zoomTo',
874
887
  'properties.easing',
875
888
  'properties.labelKey',
876
889
  'properties.sortKey',
877
890
  'properties.filterKey',
878
- 'properties.facetKey',
879
891
  'properties.slugKey',
880
892
  ]
881
893
 
882
- const builder = new MutatingForm(this, optionsFields, { umap: this })
883
- const defaultProperties = DomUtil.createFieldset(
894
+ const builder = new MutatingForm(this, shapeOptions, { umap: this })
895
+ const defaultShapeProperties = DomUtil.createFieldset(
884
896
  container,
885
897
  translate('Default properties')
886
898
  )
887
- defaultProperties.appendChild(builder.build())
899
+ defaultShapeProperties.appendChild(builder.build())
888
900
  }
889
901
 
890
902
  _editInteractionsProperties(container) {
@@ -1159,7 +1171,8 @@ export default class Umap {
1159
1171
  )
1160
1172
  this._editControls(container)
1161
1173
  this._editShapeProperties(container)
1162
- this._editDefaultProperties(container)
1174
+ this._editDefaultKeys(container)
1175
+ this.fields.edit(container)
1163
1176
  this._editInteractionsProperties(container)
1164
1177
  this.rules.edit(container)
1165
1178
  this._editTilelayer(container)
@@ -1178,6 +1191,16 @@ export default class Umap {
1178
1191
  })
1179
1192
  }
1180
1193
 
1194
+ onAnonymousSave(editUrl) {
1195
+ AlertCreation.info(
1196
+ this,
1197
+ translate('Hey, you created a map without an account!'),
1198
+ Number.Infinity,
1199
+ editUrl,
1200
+ this.properties.urls.map_send_edit_link ? this.sendEditLinkEmail.bind(this) : null
1201
+ )
1202
+ }
1203
+
1181
1204
  async save() {
1182
1205
  const geojson = {
1183
1206
  type: 'Feature',
@@ -1197,11 +1220,10 @@ export default class Umap {
1197
1220
  if (error) {
1198
1221
  return
1199
1222
  }
1200
- // TOOD: map.save may not always be the first call during save process
1223
+ // TODO: map.save may not always be the first call during save process
1201
1224
  // since SAVEMANAGER refactor
1202
1225
  if (data.login_required) {
1203
- window.onLogin = () => this.saveAll()
1204
- window.open(data.login_required)
1226
+ this.askForLogin().then(() => this.saveAll())
1205
1227
  return
1206
1228
  }
1207
1229
  this.properties.user = data.user
@@ -1209,17 +1231,9 @@ export default class Umap {
1209
1231
  this.properties.id = data.id
1210
1232
  this.permissions.setProperties(data.permissions)
1211
1233
  this.permissions.commit()
1212
- if (data.permissions?.anonymous_edit_url) {
1213
- this._leafletMap.once('saved', () => {
1214
- AlertCreation.info(
1215
- translate('Your map has been created with an anonymous account!'),
1216
- Number.Infinity,
1217
- data.permissions.anonymous_edit_url,
1218
- this.properties.urls.map_send_edit_link
1219
- ? this.sendEditLinkEmail.bind(this)
1220
- : null
1221
- )
1222
- })
1234
+ const anonymousEditUrl = data.permissions?.anonymous_edit_url
1235
+ if (anonymousEditUrl) {
1236
+ this._leafletMap.once('saved', () => this.onAnonymousSave(anonymousEditUrl))
1223
1237
  } else {
1224
1238
  this._leafletMap.once('saved', () => {
1225
1239
  Alert.success(translate('Congratulations, your map has been created!'))
@@ -1241,6 +1255,29 @@ export default class Umap {
1241
1255
  return true
1242
1256
  }
1243
1257
 
1258
+ askForLogin() {
1259
+ const promise = new Promise((resolve) => {
1260
+ const bc = new BroadcastChannel('auth')
1261
+ bc.onmessage = (event) => {
1262
+ if (event.data === 'auth:ok') {
1263
+ bc.postMessage('auth:close')
1264
+ const url = this.urls.get('whoami', { map_id: this.id })
1265
+ this.server.get(url).then(([data]) => {
1266
+ this.properties.user = data.user
1267
+ if (!this.id) {
1268
+ this.properties.permissions.owner = { ...data.user }
1269
+ }
1270
+ this.permissions.pull()
1271
+ this.render(['user', 'properties.permissions'])
1272
+ resolve()
1273
+ })
1274
+ }
1275
+ }
1276
+ })
1277
+ window.open(this.urls.get('login'))
1278
+ return promise
1279
+ }
1280
+
1244
1281
  exportProperties() {
1245
1282
  const properties = {}
1246
1283
  for (const key of Object.keys(SCHEMA)) {
@@ -1251,6 +1288,18 @@ export default class Umap {
1251
1288
  return properties
1252
1289
  }
1253
1290
 
1291
+ renameField(oldName, newName) {
1292
+ for (const datalayer of this.datalayers.active()) {
1293
+ datalayer.renameFeaturesField(oldName, newName)
1294
+ }
1295
+ }
1296
+
1297
+ deleteField(name) {
1298
+ for (const datalayer of this.datalayers.active()) {
1299
+ datalayer.deleteFeaturesField(name)
1300
+ }
1301
+ }
1302
+
1254
1303
  geometry() {
1255
1304
  /* Return a GeoJSON geometry Object */
1256
1305
  const latlng = this._leafletMap.latLng(
@@ -1269,18 +1318,51 @@ export default class Umap {
1269
1318
  this.drop.enable()
1270
1319
  this.fire('edit:enabled')
1271
1320
  this.initSyncEngine()
1272
- this.datalayers.active().forEach((datalayer) => {
1273
- if (!datalayer.isReadOnly() && datalayer._found_duplicate_id) {
1274
- datalayer._found_duplicate_id = false
1321
+ this.checkForLegacy()
1322
+ this.checkForAnonymous()
1323
+ }
1324
+
1325
+ checkForAnonymous() {
1326
+ if (
1327
+ this.permissions.isAnonymousMap() &&
1328
+ this.permissions.isOwner() &&
1329
+ this.permissions.userIsAuth()
1330
+ ) {
1331
+ this.dialog
1332
+ .confirm(
1333
+ translate('This map is anonymous, do you want to attach it to your account?')
1334
+ )
1335
+ .then(() => {
1336
+ this.permissions.attach()
1337
+ })
1338
+ }
1339
+ }
1340
+
1341
+ checkForLegacy() {
1342
+ let needSaveAlert = false
1343
+ if (this._migrated) {
1344
+ needSaveAlert = true
1345
+ delete this._migrated
1346
+ // Force user to save
1347
+ this.sync.update('properties.name', this.properties.name, this.properties.name)
1348
+ }
1349
+ for (const datalayer of this.datalayers.active()) {
1350
+ if (!datalayer.isReadOnly() && datalayer._migrated) {
1351
+ datalayer._migrated = false
1275
1352
  // Force user to resave those datalayers
1276
1353
  datalayer.sync.update(
1277
1354
  'properties.name',
1278
1355
  datalayer.properties.name,
1279
1356
  datalayer.properties.name
1280
1357
  )
1281
- Alert.info(translate('Layer has been migrated, please save the map.'))
1358
+ needSaveAlert = true
1282
1359
  }
1283
- })
1360
+ }
1361
+ if (needSaveAlert) {
1362
+ Alert.warning(
1363
+ translate('The map has been upgraded to latest version, please save it.')
1364
+ )
1365
+ }
1284
1366
  }
1285
1367
 
1286
1368
  disableEdit() {
@@ -1324,6 +1406,12 @@ export default class Umap {
1324
1406
  // Propagate will remove the fields it has already
1325
1407
  // processed
1326
1408
  fields = this.propagate(fields)
1409
+ if (fields.includes('properties.filters')) {
1410
+ this.filters.load()
1411
+ if (this.browser.isOpen()) {
1412
+ this.browser.buildFilters()
1413
+ }
1414
+ }
1327
1415
 
1328
1416
  const impacts = Utils.getImpactsFromSchema(fields)
1329
1417
  for (const impact of impacts) {
@@ -1365,7 +1453,7 @@ export default class Umap {
1365
1453
  }
1366
1454
 
1367
1455
  // This method does a targeted update of the UI,
1368
- // it whould be merged with `render`` method and the
1456
+ // it would be merged with `render`` method and the
1369
1457
  // SCHEMA at some point
1370
1458
  propagate(fields = []) {
1371
1459
  const impacts = {
@@ -1376,7 +1464,7 @@ export default class Umap {
1376
1464
  },
1377
1465
  user: () => {
1378
1466
  Utils.eachElement('.umap-user .username', (el) => {
1379
- if (this.properties.user?.id) {
1467
+ if (this.permissions.userIsAuth()) {
1380
1468
  el.textContent = this.properties.user.name
1381
1469
  }
1382
1470
  })
@@ -1483,17 +1571,22 @@ export default class Umap {
1483
1571
  `
1484
1572
  const [container, { ul }] = Utils.loadTemplateWithRefs(template)
1485
1573
  this.datalayers.reverse().map((datalayer) => {
1486
- const row = Utils.loadTemplate(
1487
- `<li class="orderable"><i class="icon icon-16 icon-drag" title="${translate('Drag to reorder')}"></i></li>`
1488
- )
1489
- datalayer.renderToolbox(row)
1574
+ const [row, { toolbox, formbox }] = Utils.loadTemplateWithRefs(`
1575
+ <li class="orderable with-toolbox ${datalayer.cssId}">
1576
+ <span data-ref=formbox class="datalayer-editable-title truncate"></span>
1577
+ <span data-ref=toolbox>
1578
+ <i class="icon icon-16 icon-drag" title="${translate('Drag to reorder')}"></i>
1579
+ </span>
1580
+ </li>
1581
+ `)
1582
+ datalayer.renderToolbox(toolbox)
1490
1583
  const builder = new MutatingForm(
1491
1584
  datalayer,
1492
1585
  [['properties.name', { handler: 'EditableText' }]],
1493
1586
  { className: 'umap-form-inline' }
1494
1587
  )
1495
1588
  const form = builder.build()
1496
- row.appendChild(form)
1589
+ formbox.appendChild(form)
1497
1590
  row.classList.toggle('off', !datalayer.isVisible())
1498
1591
  row.dataset.id = datalayer.id
1499
1592
  ul.appendChild(row)
@@ -1636,6 +1729,8 @@ export default class Umap {
1636
1729
  const fields = Object.keys(importedData.properties).map(
1637
1730
  (field) => `properties.${field}`
1638
1731
  )
1732
+ this.fields.pull()
1733
+ this.filters.load()
1639
1734
  this.render(fields)
1640
1735
  this._leafletMap._setDefaultCenter()
1641
1736
  }
@@ -1649,7 +1744,7 @@ export default class Umap {
1649
1744
  this.importRaw(rawData)
1650
1745
  } catch (e) {
1651
1746
  console.error('Error importing data', e)
1652
- U.Alert.error(
1747
+ Alert.error(
1653
1748
  translate('Invalid umap data in {filename}', { filename: file.name })
1654
1749
  )
1655
1750
  }
@@ -40,7 +40,7 @@ function _getPropertyName(field) {
40
40
  * Return an array of unique impacts.
41
41
  *
42
42
  * @param {fields} list[fields]
43
- * @param object schema object. If ommited, global U.SCHEMA will be used.
43
+ * @param object schema object. If omitted, global U.SCHEMA will be used.
44
44
  * @returns Array[string]
45
45
  */
46
46
  export function getImpactsFromSchema(fields, schema) {
@@ -400,8 +400,30 @@ export function template(str, data) {
400
400
  })
401
401
  }
402
402
 
403
+ const DATE_REGEX = [
404
+ // Format 1: "YYYY-MM-DD"
405
+ /^(?<year>\d{4})[\-\/](?<month>\d{2})[\-\/](?<day>\d{2})$/,
406
+ // Format2 : "DD-MM-YYYY"
407
+ /^(?<day>0[1-9]|[12][0-9]|3[01])[\-\/](?<month>0[1-9]|1[0-2])[\-\/](?<year>\d{4})/,
408
+ ]
409
+
403
410
  export function parseNaiveDate(value) {
404
- const naive = new Date(value)
411
+ let naive
412
+ if (!value) return undefined
413
+ value = String(value)
414
+ for (const regex of DATE_REGEX) {
415
+ const parsed = value.match(regex)
416
+ if (parsed) {
417
+ const { year, month, day } = parsed.groups
418
+ naive = new Date(year, Number.parseInt(month, 10) - 1, Number.parseInt(day, 10))
419
+ break
420
+ }
421
+ }
422
+ if (!naive) {
423
+ naive = new Date(value)
424
+ }
425
+ // Number.isNaN will always return false for invalid date
426
+ if (isNaN(naive)) return undefined
405
427
  // Let's pretend naive date are UTC, and remove time…
406
428
  return new Date(Date.UTC(naive.getFullYear(), naive.getMonth(), naive.getDate()))
407
429
  }
@@ -1,30 +1,3 @@
1
- L.Util.copyToClipboard = (textToCopy) => {
2
- // https://stackoverflow.com/a/65996386
3
- // Navigator clipboard api needs a secure context (https)
4
- if (navigator.clipboard && window.isSecureContext) {
5
- navigator.clipboard.writeText(textToCopy)
6
- } else {
7
- // Use the 'out of viewport hidden text area' trick
8
- const textArea = document.createElement('textarea')
9
- textArea.value = textToCopy
10
-
11
- // Move textarea out of the viewport so it's not visible
12
- textArea.style.position = 'absolute'
13
- textArea.style.left = '-999999px'
14
-
15
- document.body.prepend(textArea)
16
- textArea.select()
17
-
18
- try {
19
- document.execCommand('copy')
20
- } catch (error) {
21
- console.error(error)
22
- } finally {
23
- textArea.remove()
24
- }
25
- }
26
- }
27
-
28
1
  L.DomUtil.add = (tagName, className, container, content) => {
29
2
  const el = L.DomUtil.create(tagName, className, container)
30
3
  if (content) {
@@ -42,7 +15,7 @@ L.DomUtil.createFieldset = (container, legend, options) => {
42
15
  const details = L.DomUtil.create('details', options.className || '', container)
43
16
  const summary = L.DomUtil.add('summary', '', details)
44
17
  if (options.icon) L.DomUtil.createIcon(summary, options.icon)
45
- L.DomUtil.add('span', '', summary, legend)
18
+ L.DomUtil.add('h4', '', summary, legend)
46
19
  const fieldset = L.DomUtil.add('fieldset', '', details)
47
20
  details.open = options.on === true
48
21
  if (options.callback) {
@@ -94,24 +67,6 @@ L.DomUtil.createTitle = (parent, text, iconClassName, className = '', tag = 'h3'
94
67
  return title
95
68
  }
96
69
 
97
- L.DomUtil.createCopiableInput = (parent, label, value) => {
98
- const wrapper = L.DomUtil.add('div', 'copiable-input', parent)
99
- const labelEl = L.DomUtil.add('label', '', wrapper, label)
100
- const input = L.DomUtil.add('input', '', labelEl)
101
- input.type = 'text'
102
- input.readOnly = true
103
- input.value = value
104
- const button = L.DomUtil.createButtonIcon(
105
- wrapper,
106
- 'icon-copy',
107
- L._('copy'),
108
- () => L.Util.copyToClipboard(input.value),
109
- 24
110
- )
111
- button.type = 'button'
112
- return input
113
- }
114
-
115
70
  L.DomUtil.element = ({ tagName, parent, ...attrs }) => {
116
71
  const el = document.createElement(tagName)
117
72
  if (attrs.innerHTML) {
@@ -128,70 +83,6 @@ L.DomUtil.element = ({ tagName, parent, ...attrs }) => {
128
83
  return el
129
84
  }
130
85
 
131
- // From https://gist.github.com/Accudio/b9cb16e0e3df858cef0d31e38f1fe46f
132
- // convert colour in range 0-255 to the modifier used within luminance calculation
133
- L.DomUtil.colourMod = (colour) => {
134
- const sRGB = colour / 255
135
- let mod = ((sRGB + 0.055) / 1.055) ** 2.4
136
- if (sRGB < 0.03928) mod = sRGB / 12.92
137
- return mod
138
- }
139
- L.DomUtil.RGBRegex = /rgb *\( *([0-9]{1,3}) *, *([0-9]{1,3}) *, *([0-9]{1,3}) *\)/
140
- L.DomUtil.TextColorFromBackgroundColor = (el, bgcolor) => {
141
- return L.DomUtil.contrastedColor(el, bgcolor) ? '#ffffff' : '#000000'
142
- }
143
- L.DomUtil.contrastWCAG21 = (rgb) => {
144
- const [r, g, b] = rgb
145
- // luminance of inputted colour
146
- const lum =
147
- 0.2126 * L.DomUtil.colourMod(r) +
148
- 0.7152 * L.DomUtil.colourMod(g) +
149
- 0.0722 * L.DomUtil.colourMod(b)
150
- // white has a luminance of 1
151
- const whiteLum = 1
152
- const contrast = (whiteLum + 0.05) / (lum + 0.05)
153
- return contrast > 3 ? 1 : 0
154
- }
155
- L.DomUtil.colorNameToHex = (str) => {
156
- const ctx = document.createElement('canvas').getContext('2d')
157
- ctx.fillStyle = str
158
- return ctx.fillStyle
159
- }
160
- L.DomUtil.hexToRGB = (hex) => {
161
- return hex
162
- .replace(
163
- /^#?([a-f\d])([a-f\d])([a-f\d])$/i,
164
- (m, r, g, b) => `#${r}${r}${g}${g}${b}${b}`
165
- )
166
- .substring(1)
167
- .match(/.{2}/g)
168
- .map((x) => Number.parseInt(x, 16))
169
- }
170
-
171
- const _CACHE_CONSTRAST = {}
172
- L.DomUtil.contrastedColor = (el, bgcolor) => {
173
- // Return 0 for black and 1 for white
174
- // bgcolor is a human color, it can be a any keyword (purple…)
175
- if (typeof _CACHE_CONSTRAST[bgcolor] !== 'undefined') return _CACHE_CONSTRAST[bgcolor]
176
- let rgb = window.getComputedStyle(el).getPropertyValue('background-color')
177
- rgb = L.DomUtil.RGBRegex.exec(rgb)
178
- if (rgb && rgb.length === 4) {
179
- rgb = [
180
- Number.parseInt(rgb[1], 10),
181
- Number.parseInt(rgb[2], 10),
182
- Number.parseInt(rgb[3], 10),
183
- ]
184
- } else {
185
- // The element may not yet be added to the DOM, so let's try
186
- // another way
187
- const hex = L.DomUtil.colorNameToHex(bgcolor)
188
- rgb = L.DomUtil.hexToRGB(hex)
189
- }
190
- if (!rgb) return 1
191
- const out = L.DomUtil.contrastWCAG21(rgb)
192
- if (bgcolor) _CACHE_CONSTRAST[bgcolor] = out
193
- return out
194
- }
195
86
  L.LatLng.prototype.isValid = function () {
196
87
  return (
197
88
  Number.isFinite(this.lat) &&