umap-project 3.1.2__py3-none-any.whl → 3.3.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.

Potentially problematic release.


This version of umap-project might be problematic. Click here for more details.

Files changed (199) hide show
  1. umap/__init__.py +1 -1
  2. umap/locale/en/LC_MESSAGES/django.mo +0 -0
  3. umap/locale/en/LC_MESSAGES/django.po +22 -18
  4. umap/locale/fr/LC_MESSAGES/django.mo +0 -0
  5. umap/locale/fr/LC_MESSAGES/django.po +21 -17
  6. umap/management/commands/export_pictogram.py +29 -0
  7. umap/management/commands/migrate_to_S3.py +5 -1
  8. umap/management/commands/purge_old_versions.py +8 -6
  9. umap/settings/__init__.py +21 -0
  10. umap/settings/base.py +3 -0
  11. umap/static/umap/content.css +7 -2
  12. umap/static/umap/css/contextmenu.css +58 -2
  13. umap/static/umap/css/form.css +175 -45
  14. umap/static/umap/css/icon.css +97 -3
  15. umap/static/umap/css/panel.css +31 -1
  16. umap/static/umap/img/16-white.svg +21 -40
  17. umap/static/umap/img/16.svg +1 -1
  18. umap/static/umap/img/24-white.svg +9 -9
  19. umap/static/umap/img/24.svg +23 -10
  20. umap/static/umap/img/source/16-white.svg +23 -41
  21. umap/static/umap/img/source/16.svg +1 -1
  22. umap/static/umap/img/source/24-white.svg +11 -11
  23. umap/static/umap/img/source/24.svg +25 -12
  24. umap/static/umap/js/modules/browser.js +1 -1
  25. umap/static/umap/js/modules/caption.js +8 -0
  26. umap/static/umap/js/modules/data/features.js +331 -202
  27. umap/static/umap/js/modules/data/layer.js +263 -152
  28. umap/static/umap/js/modules/facets.js +2 -2
  29. umap/static/umap/js/modules/form/builder.js +11 -7
  30. umap/static/umap/js/modules/form/fields.js +66 -26
  31. umap/static/umap/js/modules/formatter.js +78 -28
  32. umap/static/umap/js/modules/importer.js +6 -1
  33. umap/static/umap/js/modules/importers/opendata.js +138 -33
  34. umap/static/umap/js/modules/importers/openrouteservice.js +140 -0
  35. umap/static/umap/js/modules/managers.js +67 -0
  36. umap/static/umap/js/modules/printer.js +107 -0
  37. umap/static/umap/js/modules/rendering/controls.js +78 -2
  38. umap/static/umap/js/modules/rendering/icon.js +116 -87
  39. umap/static/umap/js/modules/rendering/layers/classified.js +8 -7
  40. umap/static/umap/js/modules/rendering/layers/cluster.js +199 -63
  41. umap/static/umap/js/modules/rendering/map.js +6 -2
  42. umap/static/umap/js/modules/rendering/template.js +71 -1
  43. umap/static/umap/js/modules/rendering/ui.js +111 -34
  44. umap/static/umap/js/modules/rules.js +76 -23
  45. umap/static/umap/js/modules/schema.js +27 -0
  46. umap/static/umap/js/modules/share.js +19 -12
  47. umap/static/umap/js/modules/slideshow.js +1 -1
  48. umap/static/umap/js/modules/sync/updaters.js +1 -6
  49. umap/static/umap/js/modules/tableeditor.js +13 -37
  50. umap/static/umap/js/modules/templates.js +7 -6
  51. umap/static/umap/js/modules/ui/bar.js +6 -1
  52. umap/static/umap/js/modules/ui/base.js +24 -9
  53. umap/static/umap/js/modules/ui/contextmenu.js +17 -7
  54. umap/static/umap/js/modules/ui/dialog.js +7 -4
  55. umap/static/umap/js/modules/ui/panel.js +7 -0
  56. umap/static/umap/js/modules/umap.js +84 -67
  57. umap/static/umap/js/modules/utils.js +8 -7
  58. umap/static/umap/js/umap.controls.js +22 -57
  59. umap/static/umap/locale/am_ET.js +81 -9
  60. umap/static/umap/locale/am_ET.json +81 -9
  61. umap/static/umap/locale/ar.js +81 -9
  62. umap/static/umap/locale/ar.json +81 -9
  63. umap/static/umap/locale/ast.js +81 -9
  64. umap/static/umap/locale/ast.json +81 -9
  65. umap/static/umap/locale/bg.js +81 -9
  66. umap/static/umap/locale/bg.json +81 -9
  67. umap/static/umap/locale/br.js +68 -29
  68. umap/static/umap/locale/br.json +68 -29
  69. umap/static/umap/locale/ca.js +88 -16
  70. umap/static/umap/locale/ca.json +88 -16
  71. umap/static/umap/locale/cs_CZ.js +81 -9
  72. umap/static/umap/locale/cs_CZ.json +81 -9
  73. umap/static/umap/locale/da.js +48 -9
  74. umap/static/umap/locale/da.json +48 -9
  75. umap/static/umap/locale/de.js +48 -9
  76. umap/static/umap/locale/de.json +48 -9
  77. umap/static/umap/locale/el.js +58 -13
  78. umap/static/umap/locale/el.json +58 -13
  79. umap/static/umap/locale/en.js +48 -9
  80. umap/static/umap/locale/en.json +48 -9
  81. umap/static/umap/locale/en_US.json +81 -9
  82. umap/static/umap/locale/es.js +48 -9
  83. umap/static/umap/locale/es.json +48 -9
  84. umap/static/umap/locale/et.js +81 -9
  85. umap/static/umap/locale/et.json +81 -9
  86. umap/static/umap/locale/eu.js +97 -25
  87. umap/static/umap/locale/eu.json +97 -25
  88. umap/static/umap/locale/fa_IR.js +81 -9
  89. umap/static/umap/locale/fa_IR.json +81 -9
  90. umap/static/umap/locale/fi.js +81 -9
  91. umap/static/umap/locale/fi.json +81 -9
  92. umap/static/umap/locale/fr.js +48 -9
  93. umap/static/umap/locale/fr.json +48 -9
  94. umap/static/umap/locale/gl.js +81 -9
  95. umap/static/umap/locale/gl.json +81 -9
  96. umap/static/umap/locale/he.js +81 -9
  97. umap/static/umap/locale/he.json +81 -9
  98. umap/static/umap/locale/hr.js +81 -9
  99. umap/static/umap/locale/hr.json +81 -9
  100. umap/static/umap/locale/hu.js +72 -27
  101. umap/static/umap/locale/hu.json +72 -27
  102. umap/static/umap/locale/id.js +81 -9
  103. umap/static/umap/locale/id.json +81 -9
  104. umap/static/umap/locale/is.js +81 -9
  105. umap/static/umap/locale/is.json +81 -9
  106. umap/static/umap/locale/it.js +48 -9
  107. umap/static/umap/locale/it.json +48 -9
  108. umap/static/umap/locale/ja.js +81 -9
  109. umap/static/umap/locale/ja.json +81 -9
  110. umap/static/umap/locale/ko.js +81 -9
  111. umap/static/umap/locale/ko.json +81 -9
  112. umap/static/umap/locale/lt.js +81 -9
  113. umap/static/umap/locale/lt.json +81 -9
  114. umap/static/umap/locale/ms.js +81 -9
  115. umap/static/umap/locale/ms.json +81 -9
  116. umap/static/umap/locale/nl.js +48 -9
  117. umap/static/umap/locale/nl.json +48 -9
  118. umap/static/umap/locale/no.js +81 -9
  119. umap/static/umap/locale/no.json +81 -9
  120. umap/static/umap/locale/pl.js +81 -9
  121. umap/static/umap/locale/pl.json +81 -9
  122. umap/static/umap/locale/pl_PL.json +81 -9
  123. umap/static/umap/locale/pt.js +81 -9
  124. umap/static/umap/locale/pt.json +81 -9
  125. umap/static/umap/locale/pt_BR.js +91 -19
  126. umap/static/umap/locale/pt_BR.json +91 -19
  127. umap/static/umap/locale/pt_PT.js +81 -9
  128. umap/static/umap/locale/pt_PT.json +81 -9
  129. umap/static/umap/locale/ro.js +81 -9
  130. umap/static/umap/locale/ro.json +81 -9
  131. umap/static/umap/locale/ru.js +81 -9
  132. umap/static/umap/locale/ru.json +81 -9
  133. umap/static/umap/locale/sk_SK.js +81 -9
  134. umap/static/umap/locale/sk_SK.json +81 -9
  135. umap/static/umap/locale/sl.js +81 -9
  136. umap/static/umap/locale/sl.json +81 -9
  137. umap/static/umap/locale/sr.js +81 -9
  138. umap/static/umap/locale/sr.json +81 -9
  139. umap/static/umap/locale/sv.js +81 -9
  140. umap/static/umap/locale/sv.json +81 -9
  141. umap/static/umap/locale/th_TH.js +81 -9
  142. umap/static/umap/locale/th_TH.json +81 -9
  143. umap/static/umap/locale/tr.js +81 -9
  144. umap/static/umap/locale/tr.json +81 -9
  145. umap/static/umap/locale/uk_UA.js +81 -9
  146. umap/static/umap/locale/uk_UA.json +81 -9
  147. umap/static/umap/locale/vi.js +81 -9
  148. umap/static/umap/locale/vi.json +81 -9
  149. umap/static/umap/locale/vi_VN.json +81 -9
  150. umap/static/umap/locale/zh.js +81 -9
  151. umap/static/umap/locale/zh.json +81 -9
  152. umap/static/umap/locale/zh_CN.json +81 -9
  153. umap/static/umap/locale/zh_TW.Big5.json +81 -9
  154. umap/static/umap/locale/zh_TW.js +98 -26
  155. umap/static/umap/locale/zh_TW.json +98 -26
  156. umap/static/umap/map.css +325 -102
  157. umap/static/umap/vars.css +1 -0
  158. umap/static/umap/vendors/betterknown/betterknown.mjs +287 -0
  159. umap/static/umap/vendors/editable/Leaflet.Editable.js +3 -1
  160. umap/static/umap/vendors/openrouteservice/ors-js-client.js +521 -0
  161. umap/static/umap/vendors/openrouteservice/ors-js-client.js.map +1 -0
  162. umap/static/umap/vendors/simple-elevation-chart/elevation.js +63 -0
  163. umap/static/umap/vendors/simple-elevation-chart/elevation.svg +8 -0
  164. umap/static/umap/vendors/snapdom/snapdom.min.mjs +3 -0
  165. umap/storage/fs.py +3 -2
  166. umap/storage/staticfiles.py +12 -0
  167. umap/templates/base.html +4 -1
  168. umap/templates/umap/css.html +0 -4
  169. umap/templates/umap/js.html +1 -3
  170. umap/tests/base.py +9 -1
  171. umap/tests/integration/test_basics.py +3 -1
  172. umap/tests/integration/test_conditional_rules.py +79 -37
  173. umap/tests/integration/test_datalayer.py +1 -1
  174. umap/tests/integration/test_draw_polygon.py +3 -5
  175. umap/tests/integration/test_draw_polyline.py +4 -6
  176. umap/tests/integration/test_draw_route.py +178 -0
  177. umap/tests/integration/test_edit_datalayer.py +1 -1
  178. umap/tests/integration/test_edit_map.py +1 -1
  179. umap/tests/integration/test_edit_marker.py +8 -8
  180. umap/tests/integration/test_edit_polygon.py +2 -2
  181. umap/tests/integration/test_export_map.py +84 -10
  182. umap/tests/integration/test_import.py +140 -0
  183. umap/tests/integration/test_map_preview.py +1 -1
  184. umap/tests/integration/test_optimistic_merge.py +72 -12
  185. umap/tests/integration/test_share.py +1 -1
  186. umap/tests/integration/test_tableeditor.py +10 -7
  187. umap/tests/integration/test_websocket_sync.py +4 -4
  188. umap/utils.py +37 -0
  189. umap/views.py +18 -2
  190. umap_project-3.3.0.dist-info/METADATA +76 -0
  191. {umap_project-3.1.2.dist-info → umap_project-3.3.0.dist-info}/RECORD +194 -188
  192. umap/static/umap/vendors/markercluster/MarkerCluster.Default.css +0 -60
  193. umap/static/umap/vendors/markercluster/MarkerCluster.css +0 -14
  194. umap/static/umap/vendors/markercluster/leaflet.markercluster.js +0 -2
  195. umap/static/umap/vendors/markercluster/leaflet.markercluster.js.map +0 -1
  196. umap_project-3.1.2.dist-info/METADATA +0 -68
  197. {umap_project-3.1.2.dist-info → umap_project-3.3.0.dist-info}/WHEEL +0 -0
  198. {umap_project-3.1.2.dist-info → umap_project-3.3.0.dist-info}/entry_points.txt +0 -0
  199. {umap_project-3.1.2.dist-info → umap_project-3.3.0.dist-info}/licenses/LICENSE +0 -0
@@ -81,7 +81,7 @@ export class DataLayerUpdater extends BaseUpdater {
81
81
  export class FeatureUpdater extends BaseUpdater {
82
82
  getFeatureFromMetadata({ id, layerId }) {
83
83
  const datalayer = this.getDataLayerFromID(layerId)
84
- return datalayer.getFeatureById(id)
84
+ return datalayer.features.get(id)
85
85
  }
86
86
 
87
87
  // Create or update an object at a specific position
@@ -109,11 +109,6 @@ export class FeatureUpdater extends BaseUpdater {
109
109
  feature.geometry = value
110
110
  } else {
111
111
  Utils.setObjectValue(feature, key, value)
112
- if (key.startsWith('properties')) {
113
- feature.datalayer.indexProperties(feature)
114
- const name = key.replace('properties.', '')
115
- feature.datalayer.checkIndexForProperty(name)
116
- }
117
112
  }
118
113
 
119
114
  feature.render([key])
@@ -74,10 +74,10 @@ export default class TableEditor extends WithTemplate {
74
74
  const th = loadTemplate('<th><input type="checkbox" /></th>')
75
75
  const checkbox = th.firstChild
76
76
  this.elements.header.appendChild(th)
77
- for (const property of this.properties) {
77
+ for (const field of this.datalayer.fields) {
78
78
  this.elements.header.appendChild(
79
79
  loadTemplate(
80
- `<th>${property}<button data-property="${property}" class="flat" aria-label="${translate('Advanced actions')}">…</button></th>`
80
+ `<th>${field.key}<button data-property="${field.key}" class="flat" aria-label="${translate('Advanced actions')}">…</button></th>`
81
81
  )
82
82
  )
83
83
  }
@@ -91,56 +91,32 @@ export default class TableEditor extends WithTemplate {
91
91
  const bounds = this._leafletMap.getBounds()
92
92
  const inBbox = this._umap.browser.options.inBbox
93
93
  let html = ''
94
- this.datalayer.eachFeature((feature) => {
94
+ this.datalayer.features.forEach((feature) => {
95
95
  if (feature.isFiltered()) return
96
96
  if (inBbox && !feature.isOnScreen(bounds)) return
97
- const tds = this.properties.map(
98
- (prop) =>
99
- `<td tabindex="0" data-property="${prop}">${feature.properties[prop] ?? ''}</td>`
97
+ const tds = this.datalayer.fields.map(
98
+ (field) =>
99
+ `<td tabindex="0" data-property="${field.key}">${feature.properties[field.key] ?? ''}</td>`
100
100
  )
101
101
  html += `<tr data-feature="${feature.id}"><th><input type="checkbox" /></th>${tds.join('')}</tr>`
102
102
  })
103
103
  this.elements.body.innerHTML = html
104
104
  }
105
105
 
106
- resetProperties() {
107
- this.properties = this.datalayer.allProperties()
108
- if (this.properties.length === 0) {
109
- this.properties = [U.DEFAULT_LABEL_KEY, 'description']
110
- }
111
- }
112
-
113
106
  renameProperty(property) {
114
- this._umap.dialog
115
- .prompt(translate('Please enter the new name of this property'))
116
- .then(({ prompt }) => {
117
- if (!prompt || !this.datalayer.validateName(prompt)) return
118
- this.datalayer.renameProperty(property, prompt)
119
- this.open()
120
- })
107
+ this.datalayer.askForRenameProperty(property).then(() => this.open())
121
108
  }
122
109
 
123
110
  deleteProperty(property) {
124
- this._umap.dialog
125
- .confirm(
126
- translate('Are you sure you want to delete this property on all the features?')
127
- )
128
- .then(() => {
129
- this.datalayer.deleteProperty(property)
130
- this.resetProperties()
131
- this.open()
132
- })
111
+ this.datalayer.confirmDeleteProperty(property).then(() => this.open())
133
112
  }
134
113
 
135
114
  addProperty() {
136
- this.datalayer.addProperty().then(() => {
137
- this.open()
138
- })
115
+ this.datalayer.addProperty().then(() => this.open())
139
116
  }
140
117
 
141
118
  open() {
142
119
  const id = 'tableeditor:edit'
143
- this.resetProperties()
144
120
  this.renderHeaders()
145
121
  this.elements.body.innerHTML = ''
146
122
  this.renderBody()
@@ -149,7 +125,7 @@ export default class TableEditor extends WithTemplate {
149
125
  if (!this.datalayer.isRemoteLayer()) {
150
126
  const addButton = loadTemplate(`
151
127
  <button class="flat" type="button" data-ref="add">
152
- <i class="icon icon-16 icon-add"></i>${translate('Add a new property')}
128
+ <i class="icon icon-16 icon-add"></i>${translate('Add a new field')}
153
129
  </button>`)
154
130
  addButton.addEventListener('click', () => this.addProperty())
155
131
  actions.push(addButton)
@@ -181,10 +157,10 @@ export default class TableEditor extends WithTemplate {
181
157
  const property = cell.dataset.property
182
158
  const field = `properties.${property}`
183
159
  const tr = event.target.closest('tr')
184
- const feature = this.datalayer.getFeatureById(tr.dataset.feature)
160
+ const feature = this.datalayer.features.get(tr.dataset.feature)
185
161
  const handler = property === 'description' ? 'Textarea' : 'Input'
186
162
  const builder = new MutatingForm(feature, [[field, { handler }]], {
187
- id: `umap-feature-properties_${L.stamp(feature)}`,
163
+ id: `umap-feature-properties_${feature.id}`,
188
164
  })
189
165
  cell.innerHTML = ''
190
166
  cell.appendChild(builder.build())
@@ -293,7 +269,7 @@ export default class TableEditor extends WithTemplate {
293
269
  this.datalayer.hide()
294
270
  for (const row of selectedRows) {
295
271
  const id = row.dataset.feature
296
- const feature = this.datalayer.getFeatureById(id)
272
+ const feature = this.datalayer.features.get(id)
297
273
  feature.del()
298
274
  }
299
275
  this.datalayer.show()
@@ -60,20 +60,21 @@ export default class TemplateImporter {
60
60
  break
61
61
  }
62
62
  body.textContent = message
63
+ return
63
64
  }
65
+ const ul = Utils.loadTemplate('<ul></ul>')
66
+ body.appendChild(ul)
64
67
  for (const template of data.templates) {
65
68
  const item = Utils.loadTemplate(
66
- `<dl>
67
- <dt>
69
+ `<li>
68
70
  <label>
69
71
  <input type="radio" value="${template.id}" name="template" />${template.name}
70
72
  <a href="${template.url}" target="_blank"><nobr>${translate('Explore')}<i class="icon icon-16 icon-external-link"></i></nobr></a>
71
73
  </label>
72
- </dt>
73
- <dd class="text">${Utils.toHTML(template.description)}</dd>
74
- </dl>`
74
+ </li>
75
+ `
75
76
  )
76
- body.appendChild(item)
77
+ ul.appendChild(item)
77
78
  }
78
79
  tabs.querySelectorAll('button').forEach((el) => el.classList.remove('on'))
79
80
  tabs.querySelector(`[data-value="${source}"]`).classList.add('on')
@@ -213,7 +213,9 @@ export class BottomBar extends WithTemplate {
213
213
  const selected = select.options[select.selectedIndex].value
214
214
  if (!selected) return
215
215
  this._umap.datalayers.active().map((datalayer) => {
216
- datalayer.toggle(datalayer.id === selected)
216
+ if (datalayer.properties.inCaption !== false) {
217
+ datalayer.toggle(datalayer.id === selected)
218
+ }
217
219
  })
218
220
  })
219
221
  this.redraw()
@@ -263,6 +265,7 @@ const EDIT_BAR_TEMPLATE = `
263
265
  <li data-ref="multipolygon" hidden>
264
266
  <button type="button" title="${translate('Add a polygon to the current multi')}"><i class="icon icon-24 icon-multipolygon"></i></button>
265
267
  </li>
268
+ <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>
266
269
  <hr>
267
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>
268
271
  <li data-ref="import" hidden><button type="button"><i class="icon icon-24 icon-upload"></i></button></li>
@@ -293,6 +296,7 @@ export class EditBar extends WithTemplate {
293
296
  this._onClick('multiline', () => this._umap.editedFeature.ui.editor.newShape())
294
297
  this._onClick('polygon', () => this._leafletMap.editTools.startPolygon())
295
298
  this._onClick('multipolygon', () => this._umap.editedFeature.ui.editor.newShape())
299
+ this._onClick('route', () => this._leafletMap.editTools.startRoute())
296
300
  this._onClick('caption', () => this._umap.editCaption())
297
301
  this._onClick('import', () => this._umap.importer.open())
298
302
  this._onClick('templates', () => this.templateIimporter.open())
@@ -321,6 +325,7 @@ export class EditBar extends WithTemplate {
321
325
  this.elements.center.hidden = this._umap.properties.editMode !== 'advanced'
322
326
  this.elements.permissions.hidden = this._umap.properties.editMode !== 'advanced'
323
327
  this.elements.settings.hidden = this._umap.properties.editMode !== 'advanced'
328
+ this.elements.route.hidden = !this._umap.properties.ORSAPIKey
324
329
  }
325
330
 
326
331
  _addTitle(ref, label) {
@@ -12,9 +12,19 @@ export class Positioned {
12
12
  }
13
13
 
14
14
  toggleClassPosition(position) {
15
- this.container.classList.toggle('tooltip-bottom', position === 'bottom')
16
- this.container.classList.toggle('tooltip-top', position === 'top')
17
- this.container.classList.toggle('tooltip-right', position === 'right')
15
+ const positions = [
16
+ 'bottom',
17
+ 'top',
18
+ 'left',
19
+ 'right',
20
+ 'bottom-right',
21
+ 'bottom-left',
22
+ 'top-right',
23
+ 'top-left',
24
+ ]
25
+ for (const known of positions) {
26
+ this.container.classList.toggle(`tooltip-${known}`, position === known)
27
+ }
18
28
  }
19
29
 
20
30
  anchorTop(el) {
@@ -39,7 +49,6 @@ export class Positioned {
39
49
  anchorRight(el) {
40
50
  this.toggleClassPosition('right')
41
51
  const coords = this.getPosition(el)
42
- console.log(coords)
43
52
  this.setPosition({
44
53
  left: coords.right + 11,
45
54
  top: coords.top,
@@ -71,18 +80,24 @@ export class Positioned {
71
80
  }
72
81
 
73
82
  computePosition([x, y]) {
83
+ let tooltip = ''
74
84
  let left
75
85
  let top
76
- if (x < window.innerWidth / 2) {
77
- left = x
78
- } else {
79
- left = x - this.container.offsetWidth
80
- }
81
86
  if (y < window.innerHeight / 2) {
82
87
  top = Math.min(y, window.innerHeight - this.container.offsetHeight)
88
+ tooltip += 'top'
83
89
  } else {
84
90
  top = Math.max(0, y - this.container.offsetHeight)
91
+ tooltip += 'bottom'
92
+ }
93
+ if (x < window.innerWidth / 2) {
94
+ left = x
95
+ tooltip += '-left'
96
+ } else {
97
+ left = x - this.container.offsetWidth
98
+ tooltip += '-right'
85
99
  }
100
+ this.toggleClassPosition(tooltip)
86
101
  this.setPosition({ left, top })
87
102
  }
88
103
 
@@ -1,4 +1,4 @@
1
- import { loadTemplate } from '../utils.js'
1
+ import { loadTemplate, loadTemplateWithRefs } from '../utils.js'
2
2
  import { Positioned } from './base.js'
3
3
 
4
4
  export default class ContextMenu extends Positioned {
@@ -29,16 +29,21 @@ export default class ContextMenu extends Positioned {
29
29
  this.openAt([coords.left, coords.bottom], items)
30
30
  }
31
31
 
32
- openAt([left, top], items) {
33
- this.container.innerHTML = ''
32
+ addItems(items, container) {
34
33
  for (const item of items) {
35
34
  if (item === '-') {
36
- this.container.appendChild(document.createElement('hr'))
35
+ container.appendChild(document.createElement('hr'))
36
+ } else if (item.items) {
37
+ const [li, { bar }] = loadTemplateWithRefs(
38
+ `<li class="dark"><ul data-ref=bar class="icon-bar"></ul></li>`
39
+ )
40
+ this.addItems(item.items, bar)
41
+ container.appendChild(li)
37
42
  } else if (typeof item.action === 'string') {
38
43
  const li = loadTemplate(
39
44
  `<li class="${item.className || ''}"><a tabindex="0" href="${item.action}">${item.label}</a></li>`
40
45
  )
41
- this.container.appendChild(li)
46
+ container.appendChild(li)
42
47
  } else {
43
48
  let content = item.label || ''
44
49
  if (item.icon) {
@@ -51,9 +56,14 @@ export default class ContextMenu extends Positioned {
51
56
  this.close()
52
57
  item.action()
53
58
  })
54
- this.container.appendChild(li)
59
+ container.appendChild(li)
55
60
  }
56
61
  }
62
+ }
63
+
64
+ openAt([left, top], items) {
65
+ this.container.innerHTML = ''
66
+ this.addItems(items, this.container)
57
67
  // When adding contextmenu below the map container, clicking on any link will send the
58
68
  // "focusout" element on link click, preventing to trigger the click action
59
69
  const parent = document
@@ -65,7 +75,7 @@ export default class ContextMenu extends Positioned {
65
75
  } else {
66
76
  this.computePosition([left, top])
67
77
  }
68
- this.container.querySelector('li > *').focus()
78
+ this.container.querySelector('button, a').focus()
69
79
  this.container.addEventListener(
70
80
  'keydown',
71
81
  (event) => {
@@ -37,7 +37,8 @@ export default class Dialog extends WithTemplate {
37
37
  this.init()
38
38
  }
39
39
 
40
- collectFormData(formData) {
40
+ collectFormData() {
41
+ const formData = new FormData(this.elements.form)
41
42
  const object = {}
42
43
  formData.forEach((value, key) => {
43
44
  if (!Reflect.has(object, key)) {
@@ -145,6 +146,10 @@ export default class Dialog extends WithTemplate {
145
146
  return this.waitForUser()
146
147
  }
147
148
 
149
+ on(...args) {
150
+ this.dialog.addEventListener(...args)
151
+ }
152
+
148
153
  close() {
149
154
  this.toggle(false)
150
155
  this.dialog.returnValue = undefined
@@ -171,9 +176,7 @@ export default class Dialog extends WithTemplate {
171
176
  'close',
172
177
  (event) => {
173
178
  if (this.dialog.returnValue === 'accept') {
174
- const value = this.hasFormData
175
- ? this.collectFormData(new FormData(this.elements.form))
176
- : true
179
+ const value = this.hasFormData ? this.collectFormData() : true
177
180
  resolve(value)
178
181
  }
179
182
  },
@@ -88,6 +88,13 @@ export class Panel {
88
88
  this._leafletMap.invalidateSize({ pan: false })
89
89
  }
90
90
  }
91
+ scrollTo(selector) {
92
+ const fieldset = this.container.querySelector(selector)
93
+ if (!fieldset) return
94
+ fieldset.open = true
95
+ const { top, left } = fieldset.getBoundingClientRect()
96
+ this.container.scrollTo(left, top)
97
+ }
91
98
  }
92
99
 
93
100
  export class EditPanel extends Panel {
@@ -2,7 +2,6 @@ import {
2
2
  DomUtil,
3
3
  Util as LeafletUtil,
4
4
  latLngBounds,
5
- stamp,
6
5
  } from '../../vendors/leaflet/leaflet-src.esm.js'
7
6
  import {
8
7
  uMapAlert as Alert,
@@ -34,6 +33,7 @@ import Tooltip from './ui/tooltip.js'
34
33
  import URLs from './urls.js'
35
34
  import * as Utils from './utils.js'
36
35
  import { DataLayerManager } from './managers.js'
36
+ import { Importer as OpenRouteService } from './importers/openrouteservice.js'
37
37
 
38
38
  export default class Umap {
39
39
  constructor(element, geojson) {
@@ -129,7 +129,6 @@ export default class Umap {
129
129
  )
130
130
  this.tooltip = new Tooltip(this._leafletMap._controlContainer)
131
131
  this.contextmenu = new ContextMenu()
132
- this.editContextmenu = new ContextMenu({ className: 'dark', orientation: 'rows' })
133
132
  this.server = new ServerRequest()
134
133
  this.request = new Request()
135
134
  this.facets = new Facets(this)
@@ -211,7 +210,7 @@ export default class Umap {
211
210
 
212
211
  if (!this.properties.noControl) {
213
212
  this.initShortcuts()
214
- this._leafletMap.on('contextmenu', (e) => this.onContextMenu(e))
213
+ this._leafletMap.on('contextmenu', (event) => this.onContextMenu(event))
215
214
  this.onceDataLoaded(this.setViewFromQueryString)
216
215
  this.bottomBar.setup()
217
216
  this.propagate()
@@ -262,6 +261,17 @@ export default class Umap {
262
261
  return window.self !== window.top
263
262
  }
264
263
 
264
+ get fields() {
265
+ if (!this.properties.fields) this.properties.fields = []
266
+ return this.properties.fields
267
+ }
268
+
269
+ get fieldKeys() {
270
+ return this.fields
271
+ .map((field) => field.key)
272
+ .concat(...this.datalayers.active().map((dl) => dl.fieldKeys))
273
+ }
274
+
265
275
  setPropertiesFromQueryString() {
266
276
  const asBoolean = (key) => {
267
277
  const value = this.searchParams.get(key)
@@ -381,48 +391,28 @@ export default class Umap {
381
391
  }
382
392
  }
383
393
 
384
- getOwnContextMenuItems(event) {
394
+ getOwnContextMenu(event) {
385
395
  const items = []
386
- if (this.hasEditMode()) {
387
- if (this.editEnabled) {
388
- if (!this.isDirty) {
389
- items.push({
390
- label: this.help.displayLabel('STOP_EDIT'),
391
- action: () => this.disableEdit(),
392
- })
393
- }
394
- if (this.properties.enableMarkerDraw) {
395
- items.push({
396
- label: this.help.displayLabel('DRAW_MARKER'),
396
+ if (this.editEnabled) {
397
+ items.push({
398
+ items: [
399
+ {
400
+ title: this.help.displayLabel('DRAW_MARKER', false),
401
+ icon: 'icon-marker',
397
402
  action: () => this._leafletMap.editTools.startMarker(),
398
- })
399
- }
400
- if (this.properties.enablePolylineDraw) {
401
- items.push({
402
- label: this.help.displayLabel('DRAW_POLYGON'),
403
- action: () => this._leafletMap.editTools.startPolygon(),
404
- })
405
- }
406
- if (this.properties.enablePolygonDraw) {
407
- items.push({
408
- label: this.help.displayLabel('DRAW_LINE'),
403
+ },
404
+ {
405
+ title: this.help.displayLabel('DRAW_LINE', false),
406
+ icon: 'icon-polyline',
409
407
  action: () => this._leafletMap.editTools.startPolyline(),
410
- })
411
- }
412
- items.push('-')
413
- items.push({
414
- label: translate('Help'),
415
- action: () => this.help.show('edit'),
416
- })
417
- } else {
418
- items.push({
419
- label: this.help.displayLabel('TOGGLE_EDIT'),
420
- action: () => this.enableEdit(),
421
- })
422
- }
423
- }
424
- if (items.length) {
425
- items.push('-')
408
+ },
409
+ {
410
+ title: this.help.displayLabel('DRAW_POLYGON', false),
411
+ icon: 'icon-polygon',
412
+ action: () => this._leafletMap.editTools.startPolygon(),
413
+ },
414
+ ],
415
+ })
426
416
  }
427
417
  items.push(
428
418
  {
@@ -453,27 +443,32 @@ export default class Umap {
453
443
  return items
454
444
  }
455
445
 
456
- getSharedContextMenuItems(event) {
446
+ getSharedContextMenu(event) {
457
447
  const items = []
458
448
  if (this.properties.urls.routing) {
459
- items.push('-', {
449
+ items.push({
460
450
  label: translate('Directions from here'),
461
451
  action: () => this.openExternalRouting(event),
462
452
  })
463
453
  }
454
+ if (this.properties.ORSAPIKey) {
455
+ items.push({
456
+ label: translate('Compute isochrone from here'),
457
+ action: () => this.askForIsochrone(event),
458
+ })
459
+ }
464
460
  if (this.properties.urls.edit_in_osm) {
465
- items.push('-', {
461
+ items.push({
466
462
  label: translate('Edit in OpenStreetMap'),
467
463
  action: () => this.editInOSM(event),
468
464
  })
469
465
  }
466
+ if (items.length) items.unshift('-')
470
467
  return items
471
468
  }
472
469
 
473
470
  onContextMenu(event) {
474
- const items = this.getOwnContextMenuItems(event).concat(
475
- this.getSharedContextMenuItems(event)
476
- )
471
+ const items = this.getOwnContextMenu(event).concat(this.getSharedContextMenu(event))
477
472
  this.contextmenu.open(event.originalEvent, items)
478
473
  }
479
474
 
@@ -503,6 +498,10 @@ export default class Umap {
503
498
  return SCHEMA[key]?.default
504
499
  }
505
500
 
501
+ getColor() {
502
+ return this.getProperty('color')
503
+ }
504
+
506
505
  getOption(key, feature) {
507
506
  // TODO: remove when field.js does not call blindly obj.getOption anymore
508
507
  return this.getProperty(key, feature)
@@ -742,10 +741,6 @@ export default class Umap {
742
741
  return Boolean(this.datalayers.count())
743
742
  }
744
743
 
745
- allProperties() {
746
- return [].concat(...this.datalayers.active().map((dl) => dl.allProperties()))
747
- }
748
-
749
744
  sortedValues(property) {
750
745
  return []
751
746
  .concat(...this.datalayers.active().map((dl) => dl.sortedValues(property)))
@@ -853,6 +848,7 @@ export default class Umap {
853
848
  const shapeOptions = [
854
849
  'properties.color',
855
850
  'properties.iconClass',
851
+ 'properties.iconSize',
856
852
  'properties.iconUrl',
857
853
  'properties.iconOpacity',
858
854
  'properties.opacity',
@@ -1266,20 +1262,6 @@ export default class Umap {
1266
1262
  }
1267
1263
  }
1268
1264
 
1269
- toGeoJSON() {
1270
- let features = []
1271
- this.datalayers.active().map((datalayer) => {
1272
- if (datalayer.isVisible()) {
1273
- features = features.concat(datalayer.featuresToGeoJSON())
1274
- }
1275
- })
1276
- const geojson = {
1277
- type: 'FeatureCollection',
1278
- features: features,
1279
- }
1280
- return geojson
1281
- }
1282
-
1283
1265
  enableEdit() {
1284
1266
  this.editBar.redraw()
1285
1267
  document.body.classList.add('umap-edit-enabled')
@@ -1463,7 +1445,7 @@ export default class Umap {
1463
1445
 
1464
1446
  eachFeature(callback) {
1465
1447
  this.datalayers.browsable().map((datalayer) => {
1466
- if (datalayer.isVisible()) datalayer.eachFeature(callback)
1448
+ if (datalayer.isVisible()) datalayer.features.forEach(callback)
1467
1449
  })
1468
1450
  }
1469
1451
 
@@ -1519,6 +1501,7 @@ export default class Umap {
1519
1501
  const oldRank = datalayer.rank
1520
1502
  datalayer.rank = rank
1521
1503
  datalayer.sync.update('options.rank', rank, oldRank)
1504
+ datalayer.redraw()
1522
1505
  }
1523
1506
  })
1524
1507
  this.sync.commitBatch()
@@ -1735,6 +1718,12 @@ export default class Umap {
1735
1718
  if (url) window.open(url)
1736
1719
  }
1737
1720
 
1721
+ async askForIsochrone(event) {
1722
+ if (!this.properties.ORSAPIKey) return
1723
+ const importer = new OpenRouteService(this)
1724
+ importer.isochrone(event.latlng)
1725
+ }
1726
+
1738
1727
  setCenterAndZoom() {
1739
1728
  this._setCenterAndZoom(true)
1740
1729
  Alert.success(translate('The zoom and center have been modified.'))
@@ -1765,4 +1754,32 @@ export default class Umap {
1765
1754
  redo() {
1766
1755
  this.sync._undoManager.redo()
1767
1756
  }
1757
+
1758
+ async screenshot() {
1759
+ const { snapdom, preCache } = await import('../../vendors/snapdom/snapdom.min.mjs')
1760
+ const el = document.querySelector('#map')
1761
+ await preCache(el)
1762
+ const result = await snapdom(el, {
1763
+ scale: 1,
1764
+ type: 'jpg',
1765
+ fast: false,
1766
+ exclude: [
1767
+ '.leaflet-control',
1768
+ '.umap-loader',
1769
+ '.panel',
1770
+ '.umap-caption-bar',
1771
+ '.umap-main-edit-toolbox',
1772
+ '.umap-edit-bar',
1773
+ ],
1774
+ })
1775
+ return result
1776
+ }
1777
+
1778
+ async openPrinter(action) {
1779
+ if (!this._printer) {
1780
+ const Printer = (await import('./printer.js')).default
1781
+ this._printer = new Printer(this)
1782
+ }
1783
+ this._printer.open(action)
1784
+ }
1768
1785
  }
@@ -3,15 +3,16 @@ import { default as DOMPurifyInitializer } from '../../vendors/dompurify/purify.
3
3
  /**
4
4
  * Generate a pseudo-unique identifier (5 chars long, mixed-case alphanumeric)
5
5
  *
6
- * Here's the collision risk:
7
- * - for 6 chars, 1 in 100 000
8
- * - for 5 chars, 5 in 100 000
9
- * - for 4 chars, 500 in 100 000
10
- *
11
6
  * @returns string
12
7
  */
13
- export function generateId() {
14
- return btoa(Math.random().toString()).substring(10, 15)
8
+ const CHARACTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
9
+ export function generateId(length = 5) {
10
+ let result = ''
11
+ const charactersLength = CHARACTERS.length
12
+ for (let i = 0; i < length; i++) {
13
+ result += CHARACTERS.charAt(Math.floor(Math.random() * charactersLength))
14
+ }
15
+ return result
15
16
  }
16
17
 
17
18
  /**