umap-project 3.3.6__py3-none-any.whl → 3.4.0b1__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 (219) 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 +34 -28
  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 +43 -33
  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/static/umap/base.css +89 -32
  42. umap/static/umap/content.css +129 -33
  43. umap/static/umap/css/bar.css +82 -20
  44. umap/static/umap/css/browser.css +163 -0
  45. umap/static/umap/css/contextmenu.css +15 -0
  46. umap/static/umap/css/dialog.css +36 -16
  47. umap/static/umap/css/form.css +122 -32
  48. umap/static/umap/css/icon.css +46 -3
  49. umap/static/umap/css/panel.css +7 -3
  50. umap/static/umap/css/popup.css +34 -8
  51. umap/static/umap/css/tooltip.css +8 -4
  52. umap/static/umap/img/16-white.svg +26 -8
  53. umap/static/umap/img/16.svg +1 -1
  54. umap/static/umap/img/source/16-white.svg +36 -18
  55. umap/static/umap/img/source/16.svg +1 -1
  56. umap/static/umap/js/components/alerts/alert.css +69 -31
  57. umap/static/umap/js/components/alerts/alert.js +20 -2
  58. umap/static/umap/js/modules/browser.js +63 -55
  59. umap/static/umap/js/modules/caption.js +10 -7
  60. umap/static/umap/js/modules/data/features.js +82 -59
  61. umap/static/umap/js/modules/data/layer.js +56 -157
  62. umap/static/umap/js/modules/domutils.js +109 -0
  63. umap/static/umap/js/modules/filters.js +807 -0
  64. umap/static/umap/js/modules/form/builder.js +8 -5
  65. umap/static/umap/js/modules/form/fields.js +110 -220
  66. umap/static/umap/js/modules/formatter.js +24 -1
  67. umap/static/umap/js/modules/help.js +3 -2
  68. umap/static/umap/js/modules/importers/opendata.js +5 -0
  69. umap/static/umap/js/modules/importers/openrouteservice.js +6 -1
  70. umap/static/umap/js/modules/managers.js +265 -1
  71. umap/static/umap/js/modules/permissions.js +39 -31
  72. umap/static/umap/js/modules/rendering/controls.js +7 -7
  73. umap/static/umap/js/modules/rendering/icon.js +3 -8
  74. umap/static/umap/js/modules/rendering/layers/classified.js +17 -10
  75. umap/static/umap/js/modules/rendering/layers/cluster.js +5 -3
  76. umap/static/umap/js/modules/rendering/template.js +44 -8
  77. umap/static/umap/js/modules/rendering/ui.js +29 -23
  78. umap/static/umap/js/modules/rules.js +4 -3
  79. umap/static/umap/js/modules/schema.js +3 -6
  80. umap/static/umap/js/modules/share.js +4 -3
  81. umap/static/umap/js/modules/tableeditor.js +50 -38
  82. umap/static/umap/js/modules/templates.js +2 -3
  83. umap/static/umap/js/modules/ui/bar.js +42 -18
  84. umap/static/umap/js/modules/ui/dialog.js +33 -31
  85. umap/static/umap/js/modules/ui/panel.js +21 -7
  86. umap/static/umap/js/modules/ui/tooltip.js +6 -5
  87. umap/static/umap/js/modules/umap.js +149 -51
  88. umap/static/umap/js/modules/utils.js +23 -1
  89. umap/static/umap/js/umap.core.js +1 -110
  90. umap/static/umap/locale/am_ET.js +40 -14
  91. umap/static/umap/locale/am_ET.json +40 -14
  92. umap/static/umap/locale/ar.js +40 -14
  93. umap/static/umap/locale/ar.json +40 -14
  94. umap/static/umap/locale/ast.js +40 -14
  95. umap/static/umap/locale/ast.json +40 -14
  96. umap/static/umap/locale/bg.js +40 -14
  97. umap/static/umap/locale/bg.json +40 -14
  98. umap/static/umap/locale/br.js +47 -21
  99. umap/static/umap/locale/br.json +47 -21
  100. umap/static/umap/locale/ca.js +40 -14
  101. umap/static/umap/locale/ca.json +40 -14
  102. umap/static/umap/locale/cs_CZ.js +40 -14
  103. umap/static/umap/locale/cs_CZ.json +40 -14
  104. umap/static/umap/locale/da.js +40 -14
  105. umap/static/umap/locale/da.json +40 -14
  106. umap/static/umap/locale/de.js +39 -13
  107. umap/static/umap/locale/de.json +39 -13
  108. umap/static/umap/locale/el.js +40 -14
  109. umap/static/umap/locale/el.json +40 -14
  110. umap/static/umap/locale/en.js +39 -13
  111. umap/static/umap/locale/en.json +39 -13
  112. umap/static/umap/locale/en_US.json +40 -14
  113. umap/static/umap/locale/es.js +40 -14
  114. umap/static/umap/locale/es.json +40 -14
  115. umap/static/umap/locale/et.js +79 -53
  116. umap/static/umap/locale/et.json +79 -53
  117. umap/static/umap/locale/eu.js +72 -46
  118. umap/static/umap/locale/eu.json +72 -46
  119. umap/static/umap/locale/fa_IR.js +40 -14
  120. umap/static/umap/locale/fa_IR.json +40 -14
  121. umap/static/umap/locale/fi.js +40 -14
  122. umap/static/umap/locale/fi.json +40 -14
  123. umap/static/umap/locale/fr.js +39 -13
  124. umap/static/umap/locale/fr.json +39 -13
  125. umap/static/umap/locale/gl.js +40 -14
  126. umap/static/umap/locale/gl.json +40 -14
  127. umap/static/umap/locale/he.js +40 -14
  128. umap/static/umap/locale/he.json +40 -14
  129. umap/static/umap/locale/hr.js +40 -14
  130. umap/static/umap/locale/hr.json +40 -14
  131. umap/static/umap/locale/hu.js +40 -14
  132. umap/static/umap/locale/hu.json +40 -14
  133. umap/static/umap/locale/id.js +40 -14
  134. umap/static/umap/locale/id.json +40 -14
  135. umap/static/umap/locale/is.js +40 -14
  136. umap/static/umap/locale/is.json +40 -14
  137. umap/static/umap/locale/it.js +40 -14
  138. umap/static/umap/locale/it.json +40 -14
  139. umap/static/umap/locale/ja.js +40 -14
  140. umap/static/umap/locale/ja.json +40 -14
  141. umap/static/umap/locale/ko.js +40 -14
  142. umap/static/umap/locale/ko.json +40 -14
  143. umap/static/umap/locale/lt.js +40 -14
  144. umap/static/umap/locale/lt.json +40 -14
  145. umap/static/umap/locale/ms.js +40 -14
  146. umap/static/umap/locale/ms.json +40 -14
  147. umap/static/umap/locale/nl.js +40 -14
  148. umap/static/umap/locale/nl.json +40 -14
  149. umap/static/umap/locale/no.js +40 -14
  150. umap/static/umap/locale/no.json +40 -14
  151. umap/static/umap/locale/pl.js +40 -14
  152. umap/static/umap/locale/pl.json +40 -14
  153. umap/static/umap/locale/pl_PL.json +40 -14
  154. umap/static/umap/locale/pt.js +40 -14
  155. umap/static/umap/locale/pt.json +40 -14
  156. umap/static/umap/locale/pt_BR.js +40 -14
  157. umap/static/umap/locale/pt_BR.json +40 -14
  158. umap/static/umap/locale/pt_PT.js +40 -14
  159. umap/static/umap/locale/pt_PT.json +40 -14
  160. umap/static/umap/locale/ro.js +40 -14
  161. umap/static/umap/locale/ro.json +40 -14
  162. umap/static/umap/locale/ru.js +40 -14
  163. umap/static/umap/locale/ru.json +40 -14
  164. umap/static/umap/locale/sk_SK.js +40 -14
  165. umap/static/umap/locale/sk_SK.json +40 -14
  166. umap/static/umap/locale/sl.js +40 -14
  167. umap/static/umap/locale/sl.json +40 -14
  168. umap/static/umap/locale/sr.js +40 -14
  169. umap/static/umap/locale/sr.json +40 -14
  170. umap/static/umap/locale/sv.js +40 -14
  171. umap/static/umap/locale/sv.json +40 -14
  172. umap/static/umap/locale/th_TH.js +40 -14
  173. umap/static/umap/locale/th_TH.json +40 -14
  174. umap/static/umap/locale/tr.js +40 -14
  175. umap/static/umap/locale/tr.json +40 -14
  176. umap/static/umap/locale/uk_UA.js +40 -14
  177. umap/static/umap/locale/uk_UA.json +40 -14
  178. umap/static/umap/locale/vi.js +40 -14
  179. umap/static/umap/locale/vi.json +40 -14
  180. umap/static/umap/locale/vi_VN.json +40 -14
  181. umap/static/umap/locale/zh.js +40 -14
  182. umap/static/umap/locale/zh.json +40 -14
  183. umap/static/umap/locale/zh_CN.json +40 -14
  184. umap/static/umap/locale/zh_TW.Big5.json +40 -14
  185. umap/static/umap/locale/zh_TW.js +39 -13
  186. umap/static/umap/locale/zh_TW.json +39 -13
  187. umap/static/umap/map.css +60 -223
  188. umap/static/umap/unittests/utils.js +18 -0
  189. umap/static/umap/vars.css +23 -5
  190. umap/templates/umap/components/alerts/alert.html +32 -29
  191. umap/templates/umap/css.html +2 -1
  192. umap/templates/umap/login_popup_end.html +18 -9
  193. umap/templates/umap/user_map_table.html +7 -2
  194. umap/tests/integration/conftest.py +2 -6
  195. umap/tests/integration/test_anonymous_owned_map.py +89 -36
  196. umap/tests/integration/test_basics.py +25 -1
  197. umap/tests/integration/test_browser.py +37 -0
  198. umap/tests/integration/test_draw_polygon.py +2 -0
  199. umap/tests/integration/test_edit_marker.py +1 -1
  200. umap/tests/integration/test_export_map.py +19 -0
  201. umap/tests/integration/test_fields.py +522 -0
  202. umap/tests/integration/test_filters.py +617 -0
  203. umap/tests/integration/test_import.py +15 -42
  204. umap/tests/integration/test_remote_data.py +60 -4
  205. umap/tests/integration/test_share.py +4 -4
  206. umap/tests/integration/test_tableeditor.py +31 -7
  207. umap/tests/integration/test_websocket_sync.py +3 -1
  208. umap/tests/test_dashboard.py +10 -0
  209. umap/tests/test_utils.py +15 -1
  210. umap/urls.py +1 -0
  211. umap/utils.py +6 -0
  212. umap/views.py +5 -0
  213. {umap_project-3.3.6.dist-info → umap_project-3.4.0b1.dist-info}/METADATA +12 -12
  214. {umap_project-3.3.6.dist-info → umap_project-3.4.0b1.dist-info}/RECORD +217 -214
  215. umap/static/umap/js/modules/facets.js +0 -164
  216. umap/tests/integration/test_facets_browser.py +0 -279
  217. {umap_project-3.3.6.dist-info → umap_project-3.4.0b1.dist-info}/WHEEL +0 -0
  218. {umap_project-3.3.6.dist-info → umap_project-3.4.0b1.dist-info}/entry_points.txt +0 -0
  219. {umap_project-3.3.6.dist-info → umap_project-3.4.0b1.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,8 @@ 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
- import { FeatureManager } from '../managers.js'
24
+ import { FeatureManager, FieldManager } from '../managers.js'
25
+ import { Filters } from '../filters.js'
26
26
 
27
27
  export const LAYER_TYPES = [
28
28
  DefaultLayer,
@@ -89,12 +89,14 @@ export class DataLayer {
89
89
  if (!this.createdOnServer) {
90
90
  if (this.showAtLoad()) this.show()
91
91
  }
92
- if (!this._needsFetch && !this._umap.fields.length) {
92
+ if (!this._needsFetch && !this._umap.fields.size) {
93
93
  this.properties.fields = [
94
94
  { key: U.DEFAULT_LABEL_KEY, type: 'String' },
95
95
  { key: 'description', type: 'Text' },
96
96
  ]
97
97
  }
98
+ this.fields = new FieldManager(this, this._umap.dialog)
99
+ this.filters = new Filters(this, this._umap)
98
100
 
99
101
  // Only layers that are displayed on load must be hidden/shown
100
102
  // Automatically, others will be shown manually, and thus will
@@ -129,7 +131,7 @@ export class DataLayer {
129
131
  }
130
132
 
131
133
  get cssId() {
132
- return `datalayer-${stamp(this)}`
134
+ return `datalayer-${this.id}`
133
135
  }
134
136
 
135
137
  get rank() {
@@ -146,17 +148,10 @@ export class DataLayer {
146
148
  this.properties.rank = value
147
149
  }
148
150
 
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
151
  get fieldKeys() {
159
- return this.fields.map((field) => field.key)
152
+ // Needed to get a similar API from layer and uMap, but
153
+ // uMap whould return concat of all datalayers fields
154
+ return Array.from(this.fields.keys())
160
155
  }
161
156
 
162
157
  get sortKey() {
@@ -174,6 +169,18 @@ export class DataLayer {
174
169
  // Propagate will remove the fields it has already
175
170
  // processed
176
171
  fields = this.propagate(fields)
172
+ if (fields.includes('properties.fields')) {
173
+ this.fields?.pull()
174
+ if (this._umap.browser.isOpen()) {
175
+ this._umap.browser.buildFilters()
176
+ }
177
+ }
178
+ if (fields.includes('properties.filters')) {
179
+ this.filters.load()
180
+ if (this._umap.browser.isOpen()) {
181
+ this._umap.browser.buildFilters()
182
+ }
183
+ }
177
184
 
178
185
  const impacts = Utils.getImpactsFromSchema(fields)
179
186
 
@@ -448,7 +455,8 @@ export class DataLayer {
448
455
  this.inferFields(feature)
449
456
  try {
450
457
  this.showFeature(feature)
451
- } catch {
458
+ } catch (error) {
459
+ console.error(error)
452
460
  if (this._umap.editEnabled) {
453
461
  Alert.error(translate('Skipping invalid geometry'))
454
462
  }
@@ -482,88 +490,43 @@ export class DataLayer {
482
490
  }
483
491
 
484
492
  inferFields(feature) {
485
- if (!this.properties.fields) this.properties.fields = []
486
- const keys = this.fieldKeys
487
493
  for (const key in feature.properties) {
488
494
  if (typeof feature.properties[key] !== 'object') {
489
495
  if (key.indexOf('_') === 0) continue
490
- if (keys.includes(key)) continue
491
- this.properties.fields.push({ key, type: 'String' })
496
+ if (this.fields.has(key)) continue
497
+ if (this._umap.fields.has(key)) continue
498
+ let type = 'String'
499
+ if (key === 'description') type = 'Text'
500
+ this.fields.add({ key, type })
492
501
  }
493
502
  }
503
+ this.fields.push()
494
504
  }
495
505
 
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
- })
504
- }
505
-
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
- })
506
+ renameField(oldName, newName) {
507
+ this.renameFeaturesField(oldName, newName)
513
508
  }
514
509
 
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)
510
+ renameFeaturesField(oldName, newName) {
525
511
  this.features.forEach((feature) => {
526
- feature.renameProperty(oldName, newName)
512
+ feature.renameField(oldName, newName)
527
513
  })
528
- this.sync.commitBatch()
529
514
  }
530
515
 
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()
516
+ deleteField(name) {
517
+ this.deleteFeaturesField(name)
540
518
  }
541
519
 
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()
520
+ deleteFeaturesField(name) {
521
+ if (!this._umap.fields.has(name) && !this.fields.has(name)) {
522
+ this.features.forEach((feature) => {
523
+ feature.deleteField(name)
553
524
  })
554
- return promise
525
+ }
555
526
  }
556
527
 
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
528
+ eachFeature(callback) {
529
+ this.features.forEach((feature) => callback(feature))
567
530
  }
568
531
 
569
532
  sortedValues(property) {
@@ -834,7 +797,7 @@ export class DataLayer {
834
797
  const builder = new MutatingForm(this, layerFields)
835
798
  const template = `
836
799
  <details id="layer-properties">
837
- <summary>${this.layer.getName()}: ${translate('settings')}</summary>
800
+ <summary><h4>${this.layer.getName()}: ${translate('settings')}</h4></summary>
838
801
  <fieldset data-ref=fieldset></fieldset>
839
802
  </details>
840
803
  `
@@ -928,64 +891,6 @@ export class DataLayer {
928
891
  fieldset.appendChild(builder.build())
929
892
  }
930
893
 
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
894
  _editRemoteDataProperties(container) {
990
895
  // XXX I'm not sure **why** this is needed (as it's set during `this.initialize`)
991
896
  // but apparently it's needed.
@@ -1096,9 +1001,7 @@ export class DataLayer {
1096
1001
  this._editInteractionProperties(container)
1097
1002
  this._editTextPathProperties(container)
1098
1003
  this._editRemoteDataProperties(container)
1099
- if (!this.isRemoteLayer()) {
1100
- this._editFields(container)
1101
- }
1004
+ this.fields.edit(container)
1102
1005
  this.rules.edit(container)
1103
1006
 
1104
1007
  if (this._umap.properties.urls.datalayer_versions) {
@@ -1294,7 +1197,8 @@ export class DataLayer {
1294
1197
  }
1295
1198
 
1296
1199
  umapGeoJSON() {
1297
- const geojson = this._umap.formatter.toFeatureCollection(this.features.all())
1200
+ const features = this.isRemoteLayer() ? [] : this.features.all()
1201
+ const geojson = this._umap.formatter.toFeatureCollection(features)
1298
1202
  geojson._umap_options = this.properties
1299
1203
  return geojson
1300
1204
  }
@@ -1331,12 +1235,12 @@ export class DataLayer {
1331
1235
  async save() {
1332
1236
  if (this.isDeleted) return await this.saveDelete()
1333
1237
  if (!this.isRemoteLayer() && !this.isLoaded()) return
1334
- const geojson = this.umapGeoJSON()
1335
1238
  const formData = new FormData()
1336
1239
  formData.append('name', this.properties.name)
1337
1240
  formData.append('display_on_load', !!this.properties.displayOnLoad)
1338
1241
  formData.append('rank', this.rank)
1339
1242
  formData.append('settings', this.prepareProperties())
1243
+ const geojson = this.umapGeoJSON()
1340
1244
  // Filename support is shaky, don't do it for now.
1341
1245
  const blob = new Blob([JSON.stringify(geojson)], { type: 'application/json' })
1342
1246
  formData.append('geojson', blob)
@@ -1438,13 +1342,12 @@ export class DataLayer {
1438
1342
  )) {
1439
1343
  container.innerHTML = ''
1440
1344
  if (this.layer.renderLegend) return this.layer.renderLegend(container)
1441
- const keys = new Set(this.fieldKeys)
1442
1345
  const rules = new Map()
1443
1346
  for (const rule of this.rules) {
1444
1347
  rules.set(rule.condition, rule)
1445
1348
  }
1446
1349
  for (const rule of this._umap.rules) {
1447
- if (!rules.has(rule.condition) && keys.has(rule.key)) {
1350
+ if (!rules.has(rule.condition) && this.fields.has(rule.key)) {
1448
1351
  rules.set(rule.condition, rule)
1449
1352
  }
1450
1353
  }
@@ -1468,6 +1371,11 @@ export class DataLayer {
1468
1371
  'icon-eye',
1469
1372
  translate('Show/hide layer')
1470
1373
  )
1374
+ const table = DomUtil.createButtonIcon(
1375
+ container,
1376
+ 'icon-table show-on-edit',
1377
+ translate('Edit properties in a table')
1378
+ )
1471
1379
  const zoomTo = DomUtil.createButtonIcon(
1472
1380
  container,
1473
1381
  'icon-zoom',
@@ -1478,11 +1386,6 @@ export class DataLayer {
1478
1386
  'icon-edit show-on-edit',
1479
1387
  translate('Edit')
1480
1388
  )
1481
- const table = DomUtil.createButtonIcon(
1482
- container,
1483
- 'icon-table show-on-edit',
1484
- translate('Edit properties in a table')
1485
- )
1486
1389
  const remove = DomUtil.createButtonIcon(
1487
1390
  container,
1488
1391
  'icon-delete show-on-edit',
@@ -1500,16 +1403,12 @@ export class DataLayer {
1500
1403
  }
1501
1404
  DomEvent.on(toggle, 'click', () => this.toggle())
1502
1405
  DomEvent.on(zoomTo, 'click', this.zoomTo, this)
1503
- container.classList.add(this.getHidableClass())
1406
+ container.classList.add(this.cssId)
1504
1407
  container.classList.toggle('off', !this.isVisible())
1505
1408
  }
1506
1409
 
1507
1410
  getHidableElements() {
1508
- return document.querySelectorAll(`.${this.getHidableClass()}`)
1509
- }
1510
-
1511
- getHidableClass() {
1512
- return `show_with_datalayer_${stamp(this)}`
1411
+ return document.querySelectorAll(`.${this.cssId}`)
1513
1412
  }
1514
1413
 
1515
1414
  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_CONSTRAST = {}
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_CONSTRAST[bgcolor] !== 'undefined') return CACHE_CONSTRAST[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_CONSTRAST[bgcolor] = out
108
+ return out
109
+ }