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
@@ -6,6 +6,7 @@ import Orderable from './orderable.js'
6
6
  import * as Utils from './utils.js'
7
7
  import * as Icon from './rendering/icon.js'
8
8
  import { SCHEMA } from './schema.js'
9
+ import { Registry as Fields } from './data/fields.js'
9
10
 
10
11
  const EMPTY_VALUES = ['', undefined, null]
11
12
 
@@ -28,12 +29,12 @@ class Rule {
28
29
  // cf https://caniuse.com/?search=public%20class%20field
29
30
  this._condition = null
30
31
  this.OPERATORS = [
31
- ['>', this.gt],
32
- ['<', this.lt],
32
+ ['>', 'gt'],
33
+ ['<', 'lt'],
33
34
  // When sent by Django
34
- ['&lt;', this.lt],
35
- ['!=', this.not_equal],
36
- ['=', this.equal],
35
+ ['&lt;', 'lt'],
36
+ ['!=', 'not_equal'],
37
+ ['=', 'equal'],
37
38
  ]
38
39
  this.parent = parent
39
40
  this._umap = umap
@@ -47,58 +48,47 @@ class Rule {
47
48
  this.parent.render(fields)
48
49
  }
49
50
 
50
- equal(other) {
51
- return this.expected === other
52
- }
53
-
54
- not_equal(other) {
55
- return this.expected !== other
56
- }
57
-
58
- gt(other) {
59
- return other > this.expected
60
- }
61
-
62
- lt(other) {
63
- return other < this.expected
64
- }
65
-
66
51
  parse() {
67
52
  let vars = []
68
53
  this.cast = (v) => v
69
54
  this.operator = undefined
70
- for (const [sign, func] of this.OPERATORS) {
55
+ let operator = undefined
56
+ for (const [sign, funcName] of this.OPERATORS) {
71
57
  if (this.condition.includes(sign)) {
72
- this.operator = func
58
+ operator = funcName
73
59
  vars = this.condition.split(sign)
74
60
  break
75
61
  }
76
62
  }
77
63
  if (vars.length !== 2) return
78
- this.key = vars[0]
64
+ this.field = this.parent.fields.get(vars[0]) || new Fields.String(vars[0])
65
+ this.operator = this.field[operator]
79
66
  this.expected = vars[1]
80
67
  if (EMPTY_VALUES.includes(this.expected)) {
81
68
  this.cast = (v) => EMPTY_VALUES.includes(v)
82
69
  }
83
- // Special cases where we want to be lousy when checking isNaN without
84
- // coercing to a Number first because we handle multiple types.
85
- // See https://developer.mozilla.org/en-US/docs/Web/JavaScript/
86
- // Reference/Global_Objects/Number/isNaN
87
- // biome-ignore lint/suspicious/noGlobalIsNan: expected might not be a number.
88
- else if (!isNaN(this.expected)) {
89
- this.cast = Number.parseFloat
90
- } else if (['true', 'false'].includes(this.expected)) {
91
- this.cast = (v) => {
92
- if (`${v}`.toLowerCase() === 'true') return true
93
- if (`${v}`.toLowerCase() === 'false') return false
70
+ // TODO: deal with legacy rules on non typed fields
71
+ else {
72
+ this.cast = this.field.parse
73
+ if (
74
+ // Special cases where we want to be lousy when checking isNaN without
75
+ // coercing to a Number first because we handle multiple types.
76
+ // See https://developer.mozilla.org/en-US/docs/Web/JavaScript/
77
+ // Reference/Global_Objects/Number/isNaN
78
+ // biome-ignore lint/suspicious/noGlobalIsNan: expected might not be a number.
79
+ !isNaN(this.expected) &&
80
+ ['gt', 'lt'].includes(operator) &&
81
+ this.field.TYPE !== 'Number'
82
+ ) {
83
+ this.cast = Number.parseFloat
94
84
  }
95
85
  }
96
86
  this.expected = this.cast(this.expected)
97
87
  }
98
88
 
99
89
  match(props) {
100
- if (!this.operator || !this.active) return false
101
- return this.operator(this.cast(props[this.key]))
90
+ if (!this.operator || !this.active || !this.field) return false
91
+ return this.operator(this.expected, this.cast(props[this.field.key]))
102
92
  }
103
93
 
104
94
  getOption(option) {
@@ -118,6 +108,7 @@ class Rule {
118
108
  'name',
119
109
  'properties.color',
120
110
  'properties.iconClass',
111
+ 'properties.iconSize',
121
112
  'properties.iconUrl',
122
113
  'properties.iconOpacity',
123
114
  'properties.opacity',
@@ -132,7 +123,7 @@ class Rule {
132
123
  const container = document.createElement('div')
133
124
  container.appendChild(builder.build())
134
125
  const autocomplete = new AutocompleteDatalist(builder.helpers.condition.input)
135
- const properties = this.parent.fieldKeys
126
+ const properties = Array.from(this.parent.fields.keys())
136
127
  autocomplete.suggestions = properties
137
128
  autocomplete.input.addEventListener('input', (event) => {
138
129
  const value = event.target.value
@@ -140,9 +131,11 @@ class Rule {
140
131
  autocomplete.suggestions = [`${value}=`, `${value}!=`, `${value}>`, `${value}<`]
141
132
  } else if (value.endsWith('=')) {
142
133
  const key = value.split('!')[0].split('=')[0]
143
- autocomplete.suggestions = this.parent
144
- .sortedValues(key)
145
- .map((str) => `${value}${str ?? ''}`)
134
+ if (key) {
135
+ autocomplete.suggestions = this.parent
136
+ .sortedValues(key)
137
+ .map((str) => `${value}${str ?? ''}`)
138
+ }
146
139
  }
147
140
  })
148
141
  const backButton = Utils.loadTemplate(`
@@ -207,7 +200,8 @@ class Rule {
207
200
  const [li, { colorBox }] = Utils.loadTemplateWithRefs(
208
201
  `<li><span class="color-box" data-ref=colorBox></span>${this.label}</li>`
209
202
  )
210
- const bgcolor = this.properties.color || this.parent.getColor()
203
+ const bgcolor =
204
+ this.properties.fillColor || this.properties.color || this.parent.getColor()
211
205
  const symbol = this.properties.iconUrl
212
206
  colorBox.style.backgroundColor = bgcolor
213
207
  if (symbol && symbol !== SCHEMA.iconUrl.default) {
@@ -265,10 +259,10 @@ export default class Rules {
265
259
  edit(container) {
266
260
  const template = `
267
261
  <details id="rules">
268
- <summary>${translate('Conditional style rules')}</summary>
262
+ <summary><h4>${translate('Conditional style rules')}</h4></summary>
269
263
  <fieldset>
270
264
  <ul data-ref=ul></ul>
271
- <button class="umap-add" type="button" data-ref=add>${translate('Add rule')}</button>
265
+ <button type="button" data-ref=add>${translate('Add rule')}</button>
272
266
  </fieldset>
273
267
  </details>
274
268
  `
@@ -140,15 +140,12 @@ export const SCHEMA = {
140
140
  label: translate('Display the embed control'),
141
141
  default: true,
142
142
  },
143
- facetKey: {
144
- type: String,
143
+ filters: {
144
+ type: Array,
145
145
  impacts: ['ui'],
146
- helpEntries: ['facetKey'],
147
- placeholder: translate('Example: key1,key2|Label 2,key3|Label 3|checkbox'),
148
- label: translate('Filters keys'),
149
146
  },
150
147
  fields: {
151
- type: Object,
148
+ type: Array,
152
149
  },
153
150
  fill: {
154
151
  type: Boolean,
@@ -3,6 +3,7 @@ import { MutatingForm } from './form/builder.js'
3
3
  import { EXPORT_FORMATS } from './formatter.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
  export default class Share {
8
9
  constructor(umap) {
@@ -17,14 +18,14 @@ export default class Share {
17
18
  'icon-share'
18
19
  )
19
20
 
20
- DomUtil.createCopiableInput(
21
+ DOMUtils.copiableInput(
21
22
  this.container,
22
23
  translate('Link to view the map'),
23
24
  window.location.protocol + Utils.getBaseUrl()
24
25
  )
25
26
 
26
27
  if (this._umap.properties.shortUrl) {
27
- DomUtil.createCopiableInput(
28
+ DOMUtils.copiableInput(
28
29
  this.container,
29
30
  translate('Short link'),
30
31
  this._umap.properties.shortUrl
@@ -79,7 +80,7 @@ export default class Share {
79
80
  const embedTitle = DomUtil.add('h4', '', this.container, translate('Embed the map'))
80
81
  const iframe = DomUtil.create('textarea', 'umap-share-iframe', this.container)
81
82
  const urlTitle = DomUtil.add('h4', '', this.container, translate('Direct link'))
82
- const exportUrl = DomUtil.createCopiableInput(
83
+ const exportUrl = DOMUtils.copiableInput(
83
84
  this.container,
84
85
  translate('Share this link to open a customized map view'),
85
86
  ''
@@ -227,7 +228,7 @@ class IframeExporter {
227
228
 
228
229
  build() {
229
230
  const iframeUrl = this.buildUrl()
230
- let code = `<iframe width="${this.dimensions.width}" height="${this.dimensions.height}" frameborder="0" allowfullscreen allow="geolocation" src="${iframeUrl}"></iframe>`
231
+ let code = `<iframe style="width: ${this.dimensions.width}; height: ${this.dimensions.height}; border: 0;" allowfullscreen allow="geolocation" src="${iframeUrl}"></iframe>`
231
232
  if (this.options.includeFullScreenLink) {
232
233
  const fullUrl = this.buildUrl({ scrollWheelZoom: true })
233
234
  code += `<p><a href="${fullUrl}">${translate('See full screen')}</a></p>`
@@ -31,53 +31,73 @@ export default class TableEditor extends WithTemplate {
31
31
  this.elements.body.addEventListener('keydown', (event) => this.onKeyDown(event))
32
32
  this.elements.header.addEventListener('click', (event) => {
33
33
  const property = event.target.dataset.property
34
- if (property) this.openHeaderMenu(property)
34
+ const parentType = event.target.dataset.fieldParent
35
+ if (property)
36
+ this.openHeaderMenu(
37
+ property,
38
+ parentType === 'map' ? this._umap : this.datalayer
39
+ )
35
40
  })
36
41
  }
37
42
 
38
- openHeaderMenu(property) {
39
- const actions = []
40
- let filterItem
41
- if (this._umap.facets.has(property)) {
42
- filterItem = {
43
- label: translate('Remove filter for this column'),
43
+ openHeaderMenu(name, parent) {
44
+ let actionLabel
45
+ if (parent.filters.has(name)) {
46
+ actionLabel = translate('Edit filter for this field')
47
+ } else {
48
+ actionLabel = translate('Add filter for this field')
49
+ }
50
+ const actions = [
51
+ {
52
+ label: actionLabel,
44
53
  action: () => {
45
- this._umap.facets.remove(property)
54
+ parent.filters.createFilterForm(name)
46
55
  this._umap.browser.open('filters')
47
56
  },
48
- }
49
- } else {
50
- filterItem = {
51
- label: translate('Add filter for this column'),
57
+ },
58
+ {
59
+ label: translate('Edit this field'),
52
60
  action: () => {
53
- this._umap.facets.add(property)
54
- this._umap.browser.open('filters')
61
+ parent.fields.editField(name).then(() => this.open())
55
62
  },
56
- }
57
- }
58
- actions.push(filterItem)
59
- if (!this.datalayer.isRemoteLayer()) {
63
+ },
64
+ ]
65
+ // Only allow deleting fields for map and local datalayer.
66
+ if (!parent.isRemoteLayer?.()) {
60
67
  actions.push({
61
- label: translate('Rename this column'),
62
- action: () => this.renameProperty(property),
63
- })
64
- actions.push({
65
- label: translate('Delete this column'),
66
- action: () => this.deleteProperty(property),
68
+ label: translate('Delete this field'),
69
+ action: () => {
70
+ parent.fields.confirmDelete(name).then(() => this.open())
71
+ },
67
72
  })
68
73
  }
69
74
  this.contextmenu.open(event, actions)
70
75
  }
71
76
 
77
+ get fields() {
78
+ return [
79
+ ...this.datalayer.fields.all().map((field) => {
80
+ const copy = { ...field }
81
+ copy.parent = 'datalayer'
82
+ return copy
83
+ }),
84
+ ...this._umap.fields.all().map((field) => {
85
+ const copy = { ...field }
86
+ copy.parent = 'map'
87
+ return copy
88
+ }),
89
+ ]
90
+ }
91
+
72
92
  renderHeaders() {
73
93
  this.elements.header.innerHTML = ''
74
94
  const th = loadTemplate('<th><input type="checkbox" /></th>')
75
95
  const checkbox = th.firstChild
76
96
  this.elements.header.appendChild(th)
77
- for (const field of this.datalayer.fields) {
97
+ for (const field of this.fields) {
78
98
  this.elements.header.appendChild(
79
99
  loadTemplate(
80
- `<th>${field.key}<button data-property="${field.key}" class="flat" aria-label="${translate('Advanced actions')}">…</button></th>`
100
+ `<th>${field.key}<button data-property="${field.key}" data-field-parent="${field.parent}" class="flat" aria-label="${translate('Advanced actions')}">…</button></th>`
81
101
  )
82
102
  )
83
103
  }
@@ -94,7 +114,7 @@ export default class TableEditor extends WithTemplate {
94
114
  this.datalayer.features.forEach((feature) => {
95
115
  if (feature.isFiltered()) return
96
116
  if (inBbox && !feature.isOnScreen(bounds)) return
97
- const tds = this.datalayer.fields.map(
117
+ const tds = this.fields.map(
98
118
  (field) =>
99
119
  `<td tabindex="0" data-property="${field.key}">${feature.properties[field.key] ?? ''}</td>`
100
120
  )
@@ -103,16 +123,8 @@ export default class TableEditor extends WithTemplate {
103
123
  this.elements.body.innerHTML = html
104
124
  }
105
125
 
106
- renameProperty(property) {
107
- this.datalayer.askForRenameProperty(property).then(() => this.open())
108
- }
109
-
110
- deleteProperty(property) {
111
- this.datalayer.confirmDeleteProperty(property).then(() => this.open())
112
- }
113
-
114
- addProperty() {
115
- this.datalayer.addProperty().then(() => this.open())
126
+ addField() {
127
+ this.datalayer.fields.editField().then(() => this.open())
116
128
  }
117
129
 
118
130
  open() {
@@ -127,7 +139,7 @@ export default class TableEditor extends WithTemplate {
127
139
  <button class="flat" type="button" data-ref="add">
128
140
  <i class="icon icon-16 icon-add"></i>${translate('Add a new field')}
129
141
  </button>`)
130
- addButton.addEventListener('click', () => this.addProperty())
142
+ addButton.addEventListener('click', () => this.addField())
131
143
  actions.push(addButton)
132
144
 
133
145
  const deleteButton = loadTemplate(`
@@ -32,9 +32,8 @@ export default class TemplateImporter {
32
32
  const [root, { tabs, form, body, mine, confirm, confirmData }] =
33
33
  Utils.loadTemplateWithRefs(TEMPLATE)
34
34
  const uri = this.umap.urls.get('template_list')
35
- const userIsAuth = Boolean(this.umap.properties.user?.id)
36
- const defaultTab = userIsAuth ? 'mine' : 'staff'
37
- mine.hidden = !userIsAuth
35
+ const defaultTab = this.umap.permissions.userIsAuth() ? 'mine' : 'staff'
36
+ mine.hidden = !this.umap.permissions.userIsAuth()
38
37
 
39
38
  const loadTemplates = async (source) => {
40
39
  const [data, response, error] = await this.umap.server.get(
@@ -11,11 +11,16 @@ const TOP_BAR_TEMPLATE = `
11
11
  <div class="umap-left-edit-toolbox" data-ref="left">
12
12
  <div class="logo"><a class="" href="/" title="${translate('Go to the homepage')}">uMap</a></div>
13
13
  <button class="map-name flat truncate" type="button" data-ref="name"></button>
14
- <button class="share-status flat truncate" type="button" data-ref="share"></button>
15
- <button class="edit-undo round flat" type="button" data-ref="undo" disabled>
14
+ <button class="flat truncate" type="button" data-ref="share">
15
+ <i class="icon icon-16 icon-draft show-on-draft"></i><span class="share-status"></span>
16
+ </button>
17
+ <button class="anonymous truncate soft-round" type="button" data-ref="shareAnonymous" hidden>
18
+ <i class="icon icon-16 icon-anonymous"></i><span class="share-status"></span>
19
+ </button>
20
+ <button class="edit-undo flat" type="button" data-ref="undo" disabled>
16
21
  <i class="icon icon-16 icon-undo"></i>
17
22
  </button>
18
- <button class="edit-redo round flat" type="button" data-ref="redo" disabled>
23
+ <button class="edit-redo flat" type="button" data-ref="redo" disabled>
19
24
  <i class="icon icon-16 icon-redo"></i>
20
25
  </button>
21
26
  </div>
@@ -26,7 +31,7 @@ const TOP_BAR_TEMPLATE = `
26
31
  </button>
27
32
  <button class="umap-user flat" type="button" data-ref="user">
28
33
  <i class="icon icon-16 icon-profile"></i>
29
- <span class="username truncate" data-ref="username"></span>
34
+ <span class="username truncate" data-ref="username">${translate('Anonymous')}</span>
30
35
  </button>
31
36
  <button class="umap-help-link flat" type="button" title="${translate('Help')}" data-ref="help">${translate('Help')}</button>
32
37
  <button class="edit-disable round disabled-on-dirty" type="button" data-ref="view">
@@ -72,17 +77,31 @@ export class TopBar extends WithTemplate {
72
77
  duration: 5000,
73
78
  })
74
79
  })
80
+ this.elements.shareAnonymous.addEventListener('mouseover', () => {
81
+ this._umap.tooltip.open({
82
+ content: translate('Anonymous map: update who can see and edit it'),
83
+ anchor: this.elements.shareAnonymous,
84
+ position: 'bottom',
85
+ delay: 500,
86
+ duration: 5000,
87
+ })
88
+ })
75
89
  if (this._umap.properties.editMode === 'advanced') {
76
90
  this.elements.name.addEventListener('click', () => this._umap.editCaption())
77
91
  this.elements.share.addEventListener('click', () => this._umap.permissions.edit())
92
+ this.elements.shareAnonymous.addEventListener('click', () =>
93
+ this._umap.permissions.edit()
94
+ )
78
95
  }
79
96
  this.elements.user.addEventListener('click', () => {
80
- if (this._umap.properties.user?.id) {
81
- const actions = [
82
- {
83
- label: translate('New map'),
84
- action: this._umap.urls.get('map_new'),
85
- },
97
+ const actions = [
98
+ {
99
+ label: translate('New map'),
100
+ action: this._umap.urls.get('map_new'),
101
+ },
102
+ ]
103
+ if (this._umap.permissions.userIsAuth()) {
104
+ actions.push(
86
105
  {
87
106
  label: translate('My maps'),
88
107
  action: this._umap.urls.get('user_dashboard'),
@@ -90,18 +109,22 @@ export class TopBar extends WithTemplate {
90
109
  {
91
110
  label: translate('My teams'),
92
111
  action: this._umap.urls.get('user_teams'),
93
- },
94
- ]
112
+ }
113
+ )
95
114
  if (this._umap.urls.has('user_profile')) {
96
115
  actions.push({
97
116
  label: translate('My profile'),
98
117
  action: this._umap.urls.get('user_profile'),
99
118
  })
100
119
  }
101
- this._menu.openBelow(this.elements.user, actions)
120
+ } else {
121
+ actions.push({
122
+ label: translate('Login'),
123
+ action: () => this._umap.askForLogin(),
124
+ })
102
125
  }
126
+ this._menu.openBelow(this.elements.user, actions)
103
127
  })
104
-
105
128
  this.elements.peers.addEventListener('mouseover', () => {
106
129
  const connectedPeers = this._umap.sync.getPeers()
107
130
  if (!Object.keys(connectedPeers).length) return
@@ -162,10 +185,10 @@ export class TopBar extends WithTemplate {
162
185
  duration: 5000,
163
186
  })
164
187
  })
165
- this.redraw()
166
188
  }
167
189
 
168
190
  redraw() {
191
+ this.element.classList.toggle('draft', this._umap.permissions.isDraft())
169
192
  const syncEnabled = this._umap.getProperty('syncEnabled')
170
193
  this.elements.peers.hidden = !syncEnabled
171
194
  this.elements.view.disabled = this._umap.sync._undoManager.isDirty()
@@ -175,6 +198,8 @@ export class TopBar extends WithTemplate {
175
198
  this.elements.saveDraftLabel.hidden = !isDraft || isTemplate
176
199
  this.elements.saveTemplateLabel.hidden = !isTemplate
177
200
  this._umap.sync._undoManager.toggleState()
201
+ this.elements.share.hidden = this._umap.permissions.isAnonymousMap()
202
+ this.elements.shareAnonymous.hidden = !this._umap.permissions.isAnonymousMap()
178
203
  }
179
204
  }
180
205
 
@@ -211,14 +236,19 @@ export class BottomBar extends WithTemplate {
211
236
  this.elements.layers.addEventListener('change', () => {
212
237
  const select = this.elements.layers
213
238
  const selected = select.options[select.selectedIndex].value
214
- if (!selected) return
215
- this._umap.datalayers.active().map((datalayer) => {
239
+ for (const datalayer of this._umap.datalayers.active()) {
216
240
  if (datalayer.properties.inCaption !== false) {
217
- datalayer.toggle(datalayer.id === selected)
241
+ if (!selected) {
242
+ datalayer.autoVisibility = true
243
+ if (datalayer.showAtZoom() && !datalayer.isVisible()) {
244
+ datalayer.show()
245
+ }
246
+ } else {
247
+ datalayer.toggle(datalayer.id === selected)
248
+ }
218
249
  }
219
- })
250
+ }
220
251
  })
221
- this.redraw()
222
252
  }
223
253
 
224
254
  redraw() {
@@ -229,7 +259,7 @@ export class BottomBar extends WithTemplate {
229
259
  const showMenus = this._umap.getProperty('captionMenus')
230
260
  this.elements.caption.hidden = !showMenus
231
261
  this.elements.browse.hidden = !showMenus
232
- this.elements.filter.hidden = !showMenus || !this._umap.properties.facetKey
262
+ this.elements.filter.hidden = !showMenus || !this._umap.hasFilters()
233
263
  this.buildDataLayerSwitcher()
234
264
  }
235
265
 
@@ -239,7 +269,9 @@ export class BottomBar extends WithTemplate {
239
269
  if (datalayers.length < 2) {
240
270
  this.elements.layers.hidden = true
241
271
  } else {
242
- this.elements.layers.appendChild(Utils.loadTemplate(`<option value=""></option>`))
272
+ this.elements.layers.appendChild(
273
+ Utils.loadTemplate(`<option value="">${translate('All layers')}</option>`)
274
+ )
243
275
  this.elements.layers.hidden = !this._umap.getProperty('layerSwitcher')
244
276
  const visible = datalayers.filter((datalayer) => datalayer.isVisible())
245
277
  for (const datalayer of datalayers) {
@@ -267,7 +299,7 @@ const EDIT_BAR_TEMPLATE = `
267
299
  </li>
268
300
  <li data-ref="route" hidden><button type="button" data-getstarted title="${translate('Draw along routes')}"><i class="icon icon-24 icon-route"></i></button></li>
269
301
  <hr>
270
- <li data-ref="caption" hidden><button data-getstarted type="button" title="${translate('Edit map name and caption')}"><i class="icon icon-24 icon-caption"></i></button></li>
302
+ <li data-ref="caption" hidden><button data-getstarted type="button" title="${translate('Edit map name and caption')}"><i class="icon icon-24 icon-info"></i></button></li>
271
303
  <li data-ref="import" hidden><button type="button"><i class="icon icon-24 icon-upload"></i></button></li>
272
304
  <li data-ref="templates" hidden><button type="button" title="${translate('Load template')}" data-getstarted><i class="icon icon-24 icon-template"></i></button></li>
273
305
  <li data-ref="layers" hidden><button type="button" title="${translate('Manage layers')}"><i class="icon icon-24 icon-layers"></i></button></li>
@@ -78,8 +78,7 @@ export default class Dialog extends WithTemplate {
78
78
  if (!this.dialogSupported) {
79
79
  this.elements.form.addEventListener('submit', (event) => {
80
80
  event.preventDefault()
81
- this.dialog.returnValue = 'accept'
82
- this.close()
81
+ this.accept()
83
82
  })
84
83
  }
85
84
  this.dialog.addEventListener('keydown', (e) => {
@@ -118,7 +117,6 @@ export default class Dialog extends WithTemplate {
118
117
  this.elements.cancel.hidden = !dialog.cancel
119
118
  this.elements.message.textContent = dialog.message
120
119
  this.elements.message.hidden = !dialog.message
121
- this.elements.target = dialog.target || ''
122
120
  this.elements.template.innerHTML = ''
123
121
  if (dialog.template?.nodeType === 1) {
124
122
  this.elements.template.appendChild(dialog.template)
@@ -137,12 +135,13 @@ export default class Dialog extends WithTemplate {
137
135
  if (currentZIndex) {
138
136
  this.dialog.style.zIndex = currentZIndex + 1
139
137
  }
140
-
141
- this.toggle(true)
142
-
138
+ if (this.dialogSupported) {
139
+ this.dialog.show()
140
+ } else {
141
+ this.dialog.hidden = false
142
+ }
143
143
  if (this.hasFormData) this.focusable[0].focus()
144
144
  else this.elements.accept.focus()
145
-
146
145
  return this.waitForUser()
147
146
  }
148
147
 
@@ -151,37 +150,49 @@ export default class Dialog extends WithTemplate {
151
150
  }
152
151
 
153
152
  close() {
154
- this.toggle(false)
155
- this.dialog.returnValue = undefined
156
- }
157
-
158
- toggle(open = false) {
153
+ this._closing = true
159
154
  if (this.dialogSupported) {
160
- if (open) this.dialog.show()
161
- else this.dialog.close()
155
+ this.dialog.close()
162
156
  } else {
163
- this.dialog.hidden = !open
164
- if (this.elements.target && !open) {
165
- this.elements.target.focus()
166
- }
167
- if (!open) {
168
- this.dialog.dispatchEvent(new CustomEvent('close'))
169
- }
157
+ this.dialog.hidden = true
158
+ this.dialog.dispatchEvent(new CustomEvent('close'))
170
159
  }
171
160
  }
172
161
 
173
- waitForUser() {
162
+ accept() {
163
+ this.dialog.returnValue = 'accept'
174
164
  return new Promise((resolve) => {
175
165
  this.dialog.addEventListener(
176
166
  'close',
177
- (event) => {
178
- if (this.dialog.returnValue === 'accept') {
179
- const value = this.hasFormData ? this.collectFormData() : true
180
- resolve(value)
181
- }
167
+ () => {
168
+ resolve()
182
169
  },
183
170
  { once: true }
184
171
  )
172
+ this.close()
173
+ })
174
+ }
175
+
176
+ waitForUser() {
177
+ return new Promise((resolve) => {
178
+ const onClose = () => {
179
+ this._closing = false
180
+ if (this.dialog.returnValue === 'accept') {
181
+ const value = this.hasFormData ? this.collectFormData() : true
182
+ resolve(value)
183
+ }
184
+ }
185
+ const waitForClose = () => {
186
+ this.dialog.returnValue = undefined
187
+ this.dialog.addEventListener('close', () => onClose(), { once: true })
188
+ }
189
+ if (this._closing) {
190
+ // We are opening a new dialog while another is not fully closed,
191
+ // so let's first wait for that one to be fully closed
192
+ this.dialog.addEventListener('close', () => waitForClose(), { once: true })
193
+ } else {
194
+ waitForClose()
195
+ }
185
196
  })
186
197
  }
187
198