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
@@ -9,7 +9,7 @@ import {
9
9
  uMapAlert as Alert,
10
10
  uMapAlertConflict as AlertConflict,
11
11
  } from '../../components/alerts/alert.js'
12
- import { MutatingForm } from '../form/builder.js'
12
+ import { MutatingForm, Form } from '../form/builder.js'
13
13
  import { translate } from '../i18n.js'
14
14
  import { DataLayerPermissions } from '../permissions.js'
15
15
  import { Default as DefaultLayer } from '../rendering/layers/base.js'
@@ -21,8 +21,9 @@ import TableEditor from '../tableeditor.js'
21
21
  import * as Utils from '../utils.js'
22
22
  import { LineString, Point, Polygon } from './features.js'
23
23
  import Rules from '../rules.js'
24
- import Orderable from '../orderable.js'
25
24
  import { FeatureManager } from '../managers.js'
25
+ import { Filters } from '../filters.js'
26
+ import { Fields, getDefaultFields } from './fields.js'
26
27
 
27
28
  export const LAYER_TYPES = [
28
29
  DefaultLayer,
@@ -83,18 +84,18 @@ export class DataLayer {
83
84
  }
84
85
  this.connectToMap()
85
86
  this.permissions = new DataLayerPermissions(this._umap, this)
86
- this.rules = new Rules(umap, this)
87
87
 
88
88
  this._needsFetch = this.createdOnServer || this.isRemoteLayer()
89
+ if (!this._needsFetch && !this._umap.fields.size) {
90
+ this.properties.fields = getDefaultFields()
91
+ }
92
+ this.fields = new Fields(this, this._umap.dialog)
93
+ this.filters = new Filters(this, this._umap)
94
+ this.rules = new Rules(umap, this)
95
+
89
96
  if (!this.createdOnServer) {
90
97
  if (this.showAtLoad()) this.show()
91
98
  }
92
- if (!this._needsFetch && !this._umap.fields.length) {
93
- this.properties.fields = [
94
- { key: U.DEFAULT_LABEL_KEY, type: 'String' },
95
- { key: 'description', type: 'Text' },
96
- ]
97
- }
98
99
 
99
100
  // Only layers that are displayed on load must be hidden/shown
100
101
  // Automatically, others will be shown manually, and thus will
@@ -129,7 +130,7 @@ export class DataLayer {
129
130
  }
130
131
 
131
132
  get cssId() {
132
- return `datalayer-${stamp(this)}`
133
+ return `datalayer-${this.id}`
133
134
  }
134
135
 
135
136
  get rank() {
@@ -146,19 +147,6 @@ export class DataLayer {
146
147
  this.properties.rank = value
147
148
  }
148
149
 
149
- get fields() {
150
- if (!this.properties.fields) this.properties.fields = []
151
- return this.properties.fields
152
- }
153
-
154
- set fields(fields) {
155
- this.properties.fields = fields
156
- }
157
-
158
- get fieldKeys() {
159
- return this.fields.map((field) => field.key)
160
- }
161
-
162
150
  get sortKey() {
163
151
  return this.getProperty('sortKey') || U.DEFAULT_LABEL_KEY
164
152
  }
@@ -174,6 +162,18 @@ export class DataLayer {
174
162
  // Propagate will remove the fields it has already
175
163
  // processed
176
164
  fields = this.propagate(fields)
165
+ if (fields.includes('properties.fields')) {
166
+ this.fields?.pull()
167
+ if (this._umap.browser.isOpen()) {
168
+ this._umap.browser.buildFilters()
169
+ }
170
+ }
171
+ if (fields.includes('properties.filters')) {
172
+ this.filters.load()
173
+ if (this._umap.browser.isOpen()) {
174
+ this._umap.browser.buildFilters()
175
+ }
176
+ }
177
177
 
178
178
  const impacts = Utils.getImpactsFromSchema(fields)
179
179
 
@@ -202,7 +202,7 @@ export class DataLayer {
202
202
  }
203
203
 
204
204
  // This method does a targeted update of the UI,
205
- // it whould be merged with `render`` method and the
205
+ // it would be merged with `render`` method and the
206
206
  // SCHEMA at some point
207
207
  propagate(fields = []) {
208
208
  const impacts = {
@@ -225,27 +225,28 @@ export class DataLayer {
225
225
  }
226
226
 
227
227
  showAtLoad() {
228
- return this.autoLoaded && this.showAtZoom()
228
+ return this.autoVisibility && this.showAtZoom()
229
229
  }
230
230
 
231
- get autoLoaded() {
232
- if (this._autoLoaded === undefined) {
231
+ get autoVisibility() {
232
+ if (this._autoVisibility === undefined) {
233
233
  if (this._umap.datalayersFromQueryString) {
234
234
  const datalayerIds = this._umap.datalayersFromQueryString
235
- this._autoLoaded = datalayerIds.includes(this.id.toString())
235
+ this._autoVisibility = datalayerIds.includes(this.id.toString())
236
236
  if (this.properties.old_id) {
237
- this._autoLoaded =
238
- this._autoLoaded || datalayerIds.includes(this.properties.old_id.toString())
237
+ this._autoVisibility =
238
+ this._autoVisibility ||
239
+ datalayerIds.includes(this.properties.old_id.toString())
239
240
  }
240
241
  } else {
241
- this._autoLoaded = this.properties.displayOnLoad
242
+ this._autoVisibility = this.properties.displayOnLoad
242
243
  }
243
244
  }
244
- return this._autoLoaded
245
+ return this._autoVisibility
245
246
  }
246
247
 
247
- set autoLoaded(value) {
248
- this._autoLoaded = value
248
+ set autoVisibility(value) {
249
+ this._autoVisibility = value
249
250
  }
250
251
 
251
252
  insertBefore(other) {
@@ -448,7 +449,8 @@ export class DataLayer {
448
449
  this.inferFields(feature)
449
450
  try {
450
451
  this.showFeature(feature)
451
- } catch {
452
+ } catch (error) {
453
+ console.error(error)
452
454
  if (this._umap.editEnabled) {
453
455
  Alert.error(translate('Skipping invalid geometry'))
454
456
  }
@@ -482,96 +484,49 @@ export class DataLayer {
482
484
  }
483
485
 
484
486
  inferFields(feature) {
485
- if (!this.properties.fields) this.properties.fields = []
486
- const keys = this.fieldKeys
487
487
  for (const key in feature.properties) {
488
- if (typeof feature.properties[key] !== 'object') {
488
+ if (key && typeof feature.properties[key] !== 'object') {
489
489
  if (key.indexOf('_') === 0) continue
490
- if (keys.includes(key)) continue
491
- this.properties.fields.push({ key, type: 'String' })
490
+ if (this.fields.has(key)) continue
491
+ if (this._umap.fields.has(key)) continue
492
+ let type = 'String'
493
+ if (key === 'description') type = 'Text'
494
+ this.fields.add({ key, type })
492
495
  }
493
496
  }
497
+ this.fields.push()
494
498
  }
495
499
 
496
- async confirmDeleteProperty(property) {
497
- return this._umap.dialog
498
- .confirm(
499
- translate('Are you sure you want to delete this field on all the features?')
500
- )
501
- .then(() => {
502
- this.deleteProperty(property)
503
- })
500
+ renameField(oldName, newName) {
501
+ this.renameFeaturesField(oldName, newName)
504
502
  }
505
503
 
506
- async askForRenameProperty(property) {
507
- return this._umap.dialog
508
- .prompt(translate('Please enter the new name of this field'))
509
- .then(({ prompt }) => {
510
- if (!prompt || !this.validateName(prompt)) return
511
- this.renameProperty(property, prompt)
512
- })
513
- }
514
-
515
- renameProperty(oldName, newName) {
516
- this.sync.startBatch()
517
- const oldFields = Utils.CopyJSON(this.fields)
518
- for (const field of this.fields) {
519
- if (field.key === oldName) {
520
- field.key = newName
521
- break
522
- }
523
- }
524
- this.sync.update('properties.fields', this.fields, oldFields)
504
+ renameFeaturesField(oldName, newName) {
525
505
  this.features.forEach((feature) => {
526
- feature.renameProperty(oldName, newName)
506
+ feature.renameField(oldName, newName)
527
507
  })
528
- this.sync.commitBatch()
529
508
  }
530
509
 
531
- deleteProperty(property) {
532
- this.sync.startBatch()
533
- const oldFields = Utils.CopyJSON(this.fields)
534
- this.fields = this.fields.filter((field) => field.key !== property)
535
- this.sync.update('properties.fields', this.fields, oldFields)
536
- this.features.forEach((feature) => {
537
- feature.deleteProperty(property)
538
- })
539
- this.sync.commitBatch()
510
+ deleteField(name) {
511
+ this.deleteFeaturesField(name)
540
512
  }
541
513
 
542
- addProperty() {
543
- let resolve = undefined
544
- const promise = new Promise((r) => {
545
- resolve = r
546
- })
547
- this._umap.dialog
548
- .prompt(translate('Please enter the name of the property'))
549
- .then(({ prompt }) => {
550
- if (!prompt || !this.validateName(prompt)) return
551
- this.properties.fields.push({ key: prompt, type: 'String' })
552
- resolve()
514
+ deleteFeaturesField(name) {
515
+ if (!this._umap.fields.has(name) && !this.fields.has(name)) {
516
+ this.features.forEach((feature) => {
517
+ feature.deleteField(name)
553
518
  })
554
- return promise
519
+ }
555
520
  }
556
521
 
557
- validateName(name) {
558
- if (name.includes('.')) {
559
- Alert.error(translate('Name “{name}” should not contain a dot.', { name }))
560
- return false
561
- }
562
- if (this.fieldKeys.includes(name)) {
563
- Alert.error(translate('This name already exists: “{name}”', { name }))
564
- return false
565
- }
566
- return true
522
+ eachFeature(callback) {
523
+ this.features.forEach((feature) => callback(feature))
567
524
  }
568
525
 
569
- sortedValues(property) {
570
- return this.features
571
- .all()
572
- .map((feature) => feature.properties[property])
573
- .filter((val, idx, arr) => arr.indexOf(val) === idx)
574
- .sort(Utils.naturalSort)
526
+ sortedValues(key) {
527
+ const field = this.fields.get(key) || this._umap.fields.get(key)
528
+ if (!field) return []
529
+ return field.values(this.features.all()).sort(Utils.naturalSort)
575
530
  }
576
531
 
577
532
  addData(geojson, sync) {
@@ -834,7 +789,7 @@ export class DataLayer {
834
789
  const builder = new MutatingForm(this, layerFields)
835
790
  const template = `
836
791
  <details id="layer-properties">
837
- <summary>${this.layer.getName()}: ${translate('settings')}</summary>
792
+ <summary><h4>${this.layer.getName()}: ${translate('settings')}</h4></summary>
838
793
  <fieldset data-ref=fieldset></fieldset>
839
794
  </details>
840
795
  `
@@ -928,64 +883,6 @@ export class DataLayer {
928
883
  fieldset.appendChild(builder.build())
929
884
  }
930
885
 
931
- _editFields(container) {
932
- const template = `
933
- <details id="fields">
934
- <summary>${translate('Manage Fields')}</summary>
935
- <fieldset>
936
- <ul data-ref=ul></ul>
937
- <button type="button" data-ref=add><i class="icon icon-16 icon-add"></i>${translate('Add a new field')}</button>
938
- </fieldset>
939
- </details>
940
- `
941
- const [fieldset, { ul, add }] = Utils.loadTemplateWithRefs(template)
942
- add.addEventListener('click', () => {
943
- this.addProperty().then(() => {
944
- this.edit().then((panel) => {
945
- panel.scrollTo('details#fields')
946
- })
947
- })
948
- })
949
- container.appendChild(fieldset)
950
- for (const field of this.fields) {
951
- const [row, { rename, del }] = Utils.loadTemplateWithRefs(
952
- `<li class="orderable" data-key="${field.key}">
953
- <button class="icon icon-16 icon-edit" title="${translate('Rename this field')}" data-ref=rename></button>
954
- <button class="icon icon-16 icon-delete" title="${translate('Delete this field')}" data-ref=del></button>
955
- <i class="icon icon-16 icon-drag" title="${translate('Drag to reorder')}"></i>
956
- ${field.key}
957
- </li>`
958
- )
959
- ul.appendChild(row)
960
- rename.addEventListener('click', () => {
961
- this.askForRenameProperty(field.key).then(() => {
962
- this.edit().then((panel) => {
963
- panel.scrollTo('details#fields')
964
- })
965
- })
966
- })
967
- del.addEventListener('click', () => {
968
- this.confirmDeleteProperty(field.key).then(() => {
969
- this.edit().then((panel) => {
970
- panel.scrollTo('details#fields')
971
- })
972
- })
973
- })
974
- }
975
- const onReorder = (src, dst, initialIndex, finalIndex) => {
976
- const orderedKeys = Array.from(ul.querySelectorAll('li')).map(
977
- (el) => el.dataset.key
978
- )
979
- const oldFields = Utils.CopyJSON(this.properties.fields)
980
- this.properties.fields.sort(
981
- (fieldA, fieldB) =>
982
- orderedKeys.indexOf(fieldA.key) > orderedKeys.indexOf(fieldB.key)
983
- )
984
- this.sync.update('properties.fields', this.properties.fields, oldFields)
985
- }
986
- const orderable = new Orderable(ul, onReorder)
987
- }
988
-
989
886
  _editRemoteDataProperties(container) {
990
887
  // XXX I'm not sure **why** this is needed (as it's set during `this.initialize`)
991
888
  // but apparently it's needed.
@@ -1096,9 +993,7 @@ export class DataLayer {
1096
993
  this._editInteractionProperties(container)
1097
994
  this._editTextPathProperties(container)
1098
995
  this._editRemoteDataProperties(container)
1099
- if (!this.isRemoteLayer()) {
1100
- this._editFields(container)
1101
- }
996
+ this.fields.edit(container)
1102
997
  this.rules.edit(container)
1103
998
 
1104
999
  if (this._umap.properties.urls.datalayer_versions) {
@@ -1223,7 +1118,7 @@ export class DataLayer {
1223
1118
  // From now on, do not try to how/hide
1224
1119
  // automatically this layer, as user
1225
1120
  // has taken control on this.
1226
- this.autoLoaded = false
1121
+ this.autoVisibility = false
1227
1122
  let display = force
1228
1123
  if (force === undefined) {
1229
1124
  if (!this.isVisible()) display = true
@@ -1294,7 +1189,8 @@ export class DataLayer {
1294
1189
  }
1295
1190
 
1296
1191
  umapGeoJSON() {
1297
- const geojson = this._umap.formatter.toFeatureCollection(this.features.all())
1192
+ const features = this.isRemoteLayer() ? [] : this.features.all()
1193
+ const geojson = this._umap.formatter.toFeatureCollection(features)
1298
1194
  geojson._umap_options = this.properties
1299
1195
  return geojson
1300
1196
  }
@@ -1331,12 +1227,12 @@ export class DataLayer {
1331
1227
  async save() {
1332
1228
  if (this.isDeleted) return await this.saveDelete()
1333
1229
  if (!this.isRemoteLayer() && !this.isLoaded()) return
1334
- const geojson = this.umapGeoJSON()
1335
1230
  const formData = new FormData()
1336
1231
  formData.append('name', this.properties.name)
1337
1232
  formData.append('display_on_load', !!this.properties.displayOnLoad)
1338
1233
  formData.append('rank', this.rank)
1339
1234
  formData.append('settings', this.prepareProperties())
1235
+ const geojson = this.umapGeoJSON()
1340
1236
  // Filename support is shaky, don't do it for now.
1341
1237
  const blob = new Blob([JSON.stringify(geojson)], { type: 'application/json' })
1342
1238
  formData.append('geojson', blob)
@@ -1438,13 +1334,15 @@ export class DataLayer {
1438
1334
  )) {
1439
1335
  container.innerHTML = ''
1440
1336
  if (this.layer.renderLegend) return this.layer.renderLegend(container)
1441
- const keys = new Set(this.fieldKeys)
1442
1337
  const rules = new Map()
1443
1338
  for (const rule of this.rules) {
1444
1339
  rules.set(rule.condition, rule)
1445
1340
  }
1446
1341
  for (const rule of this._umap.rules) {
1447
- if (!rules.has(rule.condition) && keys.has(rule.key)) {
1342
+ if (
1343
+ !rules.has(rule.condition) &&
1344
+ (this.fields.has(rule.field.key) || this._umap.fields.has(rule.field.key))
1345
+ ) {
1448
1346
  rules.set(rule.condition, rule)
1449
1347
  }
1450
1348
  }
@@ -1468,6 +1366,11 @@ export class DataLayer {
1468
1366
  'icon-eye',
1469
1367
  translate('Show/hide layer')
1470
1368
  )
1369
+ const table = DomUtil.createButtonIcon(
1370
+ container,
1371
+ 'icon-table show-on-edit',
1372
+ translate('Edit properties in a table')
1373
+ )
1471
1374
  const zoomTo = DomUtil.createButtonIcon(
1472
1375
  container,
1473
1376
  'icon-zoom',
@@ -1478,11 +1381,6 @@ export class DataLayer {
1478
1381
  'icon-edit show-on-edit',
1479
1382
  translate('Edit')
1480
1383
  )
1481
- const table = DomUtil.createButtonIcon(
1482
- container,
1483
- 'icon-table show-on-edit',
1484
- translate('Edit properties in a table')
1485
- )
1486
1384
  const remove = DomUtil.createButtonIcon(
1487
1385
  container,
1488
1386
  'icon-delete show-on-edit',
@@ -1500,16 +1398,12 @@ export class DataLayer {
1500
1398
  }
1501
1399
  DomEvent.on(toggle, 'click', () => this.toggle())
1502
1400
  DomEvent.on(zoomTo, 'click', this.zoomTo, this)
1503
- container.classList.add(this.getHidableClass())
1401
+ container.classList.add(this.cssId)
1504
1402
  container.classList.toggle('off', !this.isVisible())
1505
1403
  }
1506
1404
 
1507
1405
  getHidableElements() {
1508
- return document.querySelectorAll(`.${this.getHidableClass()}`)
1509
- }
1510
-
1511
- getHidableClass() {
1512
- return `show_with_datalayer_${stamp(this)}`
1406
+ return document.querySelectorAll(`.${this.cssId}`)
1513
1407
  }
1514
1408
 
1515
1409
  propagateDelete() {
@@ -0,0 +1,109 @@
1
+ // Utils that needs the DOM
2
+ import * as Utils from './utils.js'
3
+ import { translate } from './i18n.js'
4
+ import Tooltip from './ui/tooltip.js'
5
+
6
+ export const copyToClipboard = (textToCopy) => {
7
+ const tooltip = new Tooltip()
8
+ // https://stackoverflow.com/a/65996386
9
+ // Navigator clipboard api needs a secure context (https)
10
+ if (navigator.clipboard && window.isSecureContext) {
11
+ navigator.clipboard.writeText(textToCopy)
12
+ } else {
13
+ // Use the 'out of viewport hidden text area' trick
14
+ const textArea = document.createElement('textarea')
15
+ textArea.value = textToCopy
16
+
17
+ // Move textarea out of the viewport so it's not visible
18
+ textArea.style.position = 'absolute'
19
+ textArea.style.left = '-999999px'
20
+
21
+ document.body.prepend(textArea)
22
+ textArea.select()
23
+
24
+ try {
25
+ document.execCommand('copy')
26
+ } catch (error) {
27
+ console.error(error)
28
+ } finally {
29
+ textArea.remove()
30
+ }
31
+ }
32
+ tooltip.open({ content: translate('✅ Copied!'), duration: 5000 })
33
+ }
34
+
35
+ export const copiableInput = (parent, label, value) => {
36
+ const [container, { input, button }] = Utils.loadTemplateWithRefs(`
37
+ <div class="copiable-input">
38
+ <label>${label}<input type="text" readOnly value="${value}" data-ref=input /></label>
39
+ <button type="button" class="icon icon-24 icon-copy" title="${translate('copy')}" data-ref=button></button>
40
+ </div>
41
+ `)
42
+ button.addEventListener('click', () => copyToClipboard(input.value))
43
+ parent.appendChild(container)
44
+ return input
45
+ }
46
+
47
+ // From https://gist.github.com/Accudio/b9cb16e0e3df858cef0d31e38f1fe46f
48
+ // convert colour in range 0-255 to the modifier used within luminance calculation
49
+ const colourMod = (colour) => {
50
+ const sRGB = colour / 255
51
+ let mod = ((sRGB + 0.055) / 1.055) ** 2.4
52
+ if (sRGB < 0.03928) mod = sRGB / 12.92
53
+ return mod
54
+ }
55
+ const RGBRegex = /rgb *\( *([0-9]{1,3}) *, *([0-9]{1,3}) *, *([0-9]{1,3}) *\)/
56
+
57
+ export const textColorFromBackgroundColor = (el, bgcolor) => {
58
+ return contrastedColor(el, bgcolor) ? '#ffffff' : '#000000'
59
+ }
60
+
61
+ const contrastWCAG21 = (rgb) => {
62
+ const [r, g, b] = rgb
63
+ // luminance of inputted colour
64
+ const lum = 0.2126 * colourMod(r) + 0.7152 * colourMod(g) + 0.0722 * colourMod(b)
65
+ // white has a luminance of 1
66
+ const whiteLum = 1
67
+ const contrast = (whiteLum + 0.05) / (lum + 0.05)
68
+ return contrast > 3 ? 1 : 0
69
+ }
70
+ const colorNameToHex = (str) => {
71
+ const ctx = document.createElement('canvas').getContext('2d')
72
+ ctx.fillStyle = str
73
+ return ctx.fillStyle
74
+ }
75
+ export const hexToRGB = (hex) => {
76
+ return hex
77
+ .replace(
78
+ /^#?([a-f\d])([a-f\d])([a-f\d])$/i,
79
+ (m, r, g, b) => `#${r}${r}${g}${g}${b}${b}`
80
+ )
81
+ .substring(1)
82
+ .match(/.{2}/g)
83
+ .map((x) => Number.parseInt(x, 16))
84
+ }
85
+
86
+ const CACHE_CONTRAST = {}
87
+ export const contrastedColor = (el, bgcolor) => {
88
+ // Return 0 for black and 1 for white
89
+ // bgcolor is a human color, it can be a any keyword (purple…)
90
+ if (typeof CACHE_CONTRAST[bgcolor] !== 'undefined') return CACHE_CONTRAST[bgcolor]
91
+ let rgb = window.getComputedStyle(el).getPropertyValue('background-color')
92
+ rgb = RGBRegex.exec(rgb)
93
+ if (rgb && rgb.length === 4) {
94
+ rgb = [
95
+ Number.parseInt(rgb[1], 10),
96
+ Number.parseInt(rgb[2], 10),
97
+ Number.parseInt(rgb[3], 10),
98
+ ]
99
+ } else {
100
+ // The element may not yet be added to the DOM, so let's try
101
+ // another way
102
+ const hex = colorNameToHex(bgcolor)
103
+ rgb = hexToRGB(hex)
104
+ }
105
+ if (!rgb) return 1
106
+ const out = contrastWCAG21(rgb)
107
+ if (bgcolor) CACHE_CONTRAST[bgcolor] = out
108
+ return out
109
+ }