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
@@ -1,4 +1,8 @@
1
1
  import * as Utils from './utils.js'
2
+ import { translate } from './i18n.js'
3
+ import Orderable from './orderable.js'
4
+ import { uMapAlert as Alert } from '../components/alerts/alert.js'
5
+ import { Form } from './form/builder.js'
2
6
 
3
7
  export class DataLayerManager extends Object {
4
8
  add(datalayer) {
@@ -59,7 +63,7 @@ export class FeatureManager extends Map {
59
63
  if (this.has(feature.id)) {
60
64
  console.error('Duplicate id', feature, this.get(feature.id))
61
65
  feature.id = Utils.generateId()
62
- feature.datalayer._found_duplicate_id = true
66
+ feature.datalayer._migrated = true
63
67
  }
64
68
  this.set(feature.id, feature)
65
69
  }
@@ -112,3 +116,263 @@ export class FeatureManager extends Map {
112
116
  return this.all()[index - 1]
113
117
  }
114
118
  }
119
+
120
+ export class FieldManager extends Map {
121
+ constructor(parent, dialog) {
122
+ super()
123
+ this.parent = parent
124
+ this.dialog = dialog
125
+ this.parent.properties.fields ??= []
126
+ this.pull()
127
+ }
128
+
129
+ pull() {
130
+ this.clear()
131
+ for (const field of this.parent.properties.fields) {
132
+ this.add(field)
133
+ }
134
+ }
135
+
136
+ push() {
137
+ this.parent.properties.fields = this.all().map((field) => {
138
+ // We don't want to keep the reference, otherwise editing
139
+ // it will also change the old value
140
+ return { ...field }
141
+ })
142
+ }
143
+
144
+ async commit() {
145
+ return new Promise((resolve) => {
146
+ const oldFields = Utils.CopyJSON(this.parent.properties.fields)
147
+ resolve()
148
+ this.push()
149
+ this.parent.sync.update(
150
+ 'properties.fields',
151
+ this.parent.properties.fields,
152
+ oldFields
153
+ )
154
+ })
155
+ }
156
+
157
+ add(field) {
158
+ if (!field?.key) {
159
+ console.error('Invalid field', field)
160
+ return
161
+ }
162
+ field.type ??= 'String'
163
+ // Copy object, so not to affect original
164
+ // when edited.
165
+ this.set(field.key, { ...field })
166
+ this.push()
167
+ }
168
+
169
+ delete(key) {
170
+ super.delete(key)
171
+ this.push()
172
+ }
173
+
174
+ all() {
175
+ return Array.from(this.values())
176
+ }
177
+
178
+ edit(container) {
179
+ const [root, { ul, add, manageFilters }] = Utils.loadTemplateWithRefs(`
180
+ <details id="fields-management">
181
+ <summary><h4>${translate('Manage Fields')}</h4></summary>
182
+ <fieldset>
183
+ <ul data-ref=ul></ul>
184
+ <div class="button-bar half">
185
+ <button type="button" data-ref=add>${translate('Add a new field')}</button>
186
+ <button type="button" data-ref="manageFilters">${translate('Manage filters')}</button>
187
+ </div>
188
+ </fieldset>
189
+ </details>
190
+ `)
191
+ container.appendChild(root)
192
+ add.hidden = this.parent.isRemoteLayer?.()
193
+ add.addEventListener('click', () => {
194
+ this.editField().then(() => {
195
+ this.parent.edit().then((panel) => {
196
+ panel.scrollTo('details#fields-management')
197
+ })
198
+ })
199
+ })
200
+ manageFilters.addEventListener('click', () => this.parent.filters.edit())
201
+ for (const field of this.all()) {
202
+ const [row, { edit, del, addFilter, editFilter }] = Utils.loadTemplateWithRefs(
203
+ `<li class="orderable with-toolbox" data-key="${field.key}">
204
+ <span>
205
+ <i class="icon icon-16 icon-field-${field.type}" title="${field.type}"></i>
206
+ ${field.key}
207
+ </span>
208
+ <span>
209
+ <button class="icon icon-16 icon-edit" title="${translate('Edit this field')}" data-ref=edit></button>
210
+ <button class="icon icon-16 icon-filters" title="${translate('Edit filter')}" data-ref=editFilter></button>
211
+ <button class="icon icon-16 icon-filters-empty" title="${translate('Add a filter for this field')}" data-ref=addFilter></button>
212
+ <button class="icon icon-16 icon-delete" title="${translate('Delete this field')}" data-ref=del></button>
213
+ <i class="icon icon-16 icon-drag" title="${translate('Drag to reorder')}"></i>
214
+ </span>
215
+ </li>`
216
+ )
217
+ editFilter.hidden = !this.parent.filters.has(field.key)
218
+ addFilter.hidden = this.parent.filters.has(field.key)
219
+ del.hidden = this.parent.isRemoteLayer?.()
220
+ editFilter.addEventListener('click', () =>
221
+ this.parent.filters.createFilterForm(field.key)
222
+ )
223
+ addFilter.addEventListener('click', () =>
224
+ this.parent.filters.createFilterForm(field.key)
225
+ )
226
+ ul.appendChild(row)
227
+ edit.addEventListener('click', () => {
228
+ this.editField(field.key).then(() => {
229
+ this.parent.edit().then((panel) => {
230
+ panel.scrollTo('details#fields-management')
231
+ })
232
+ })
233
+ })
234
+ del.addEventListener('click', () => {
235
+ this.confirmDelete(field.key).then(() => {
236
+ this.parent.edit().then((panel) => {
237
+ panel.scrollTo('details#fields-management')
238
+ })
239
+ })
240
+ })
241
+ }
242
+ const onReorder = (src, dst, initialIndex, finalIndex) => {
243
+ const orderedKeys = Array.from(ul.querySelectorAll('li')).map(
244
+ (el) => el.dataset.key
245
+ )
246
+ const oldFields = Utils.CopyJSON(this.parent.properties.fields)
247
+ const copy = Object.fromEntries(this)
248
+ this.clear()
249
+ for (const key of orderedKeys) {
250
+ this.add(copy[key])
251
+ }
252
+ this.parent.sync.update(
253
+ 'properties.fields',
254
+ this.parent.properties.fields,
255
+ oldFields
256
+ )
257
+ }
258
+ const orderable = new Orderable(ul, onReorder)
259
+ }
260
+
261
+ async editField(name) {
262
+ if (!name && this.parent.isRemoteLayer?.()) return
263
+ const FIELD_TYPES = [
264
+ 'String',
265
+ 'Text',
266
+ 'Number',
267
+ 'Date',
268
+ 'Datetime',
269
+ 'Enum',
270
+ 'Boolean',
271
+ ]
272
+ const field = this.get(name) || {}
273
+ const metadatas = [
274
+ [
275
+ 'key',
276
+ {
277
+ handler: 'BlurInput',
278
+ label: translate('Field Name'),
279
+ disabled: this.parent.isRemoteLayer?.(),
280
+ },
281
+ ],
282
+ [
283
+ 'type',
284
+ {
285
+ handler: 'Select',
286
+ selectOptions: FIELD_TYPES,
287
+ label: translate('Field Type'),
288
+ },
289
+ ],
290
+ ]
291
+ const form = new Form(field, metadatas)
292
+
293
+ const [container, { body, addFilter }] = Utils.loadTemplateWithRefs(`
294
+ <div>
295
+ <h3>${translate('Manage field')}</h3>
296
+ <div data-ref=body></div>
297
+ <button type="button" data-ref=addFilter hidden><i class="icon icon-16 icon-filters"></i>${translate('Add filter for this field')}</button>
298
+ </div>
299
+ `)
300
+ body.appendChild(form.build())
301
+ if (this.parent.filters) {
302
+ addFilter.addEventListener('click', () => {
303
+ this.dialog.accept()
304
+ this.parent.filters.createFilterForm(field.key)
305
+ })
306
+ addFilter.hidden = false
307
+ }
308
+
309
+ return this.dialog.open({ template: container }).then(() => {
310
+ if (!this.validateName(field.key, field.key !== name)) {
311
+ this.pull()
312
+ return
313
+ }
314
+ this.parent.sync.startBatch()
315
+ const oldFields = Utils.CopyJSON(this.parent.properties.fields)
316
+ if (!name) {
317
+ this.add(field)
318
+ } else if (name !== field.key) {
319
+ this.clear()
320
+ // Keep order on rename
321
+ for (const old of oldFields) {
322
+ if (old.key === name) {
323
+ this.add(field)
324
+ } else {
325
+ this.add(old)
326
+ }
327
+ }
328
+ this.parent.renameField(name, field.key)
329
+ } else {
330
+ this.push()
331
+ }
332
+ this.parent.sync.update(
333
+ 'properties.fields',
334
+ this.parent.properties.fields,
335
+ oldFields
336
+ )
337
+ this.parent.sync.commitBatch()
338
+ this.parent.render(['properties.fields'])
339
+ })
340
+ }
341
+
342
+ validateName(name, isNew = false) {
343
+ if (!name) {
344
+ Alert.error(translate('Name cannot be empty.'))
345
+ return false
346
+ }
347
+ if (name.includes('.')) {
348
+ Alert.error(translate('Name “{name}” should not contain a dot.', { name }))
349
+ return false
350
+ }
351
+ if (isNew && this.has(name)) {
352
+ Alert.error(translate('This name already exists: “{name}”', { name }))
353
+ return false
354
+ }
355
+ return true
356
+ }
357
+
358
+ async confirmDelete(name) {
359
+ return this.dialog
360
+ .confirm(translate('Are you sure you want to delete this field on all the data?'))
361
+ .then(() => {
362
+ this.parent.sync.startBatch()
363
+ const oldFields = Utils.CopyJSON(this.parent.properties.fields)
364
+ this.delete(name)
365
+ this.push()
366
+ if (this.parent.filters.has(name)) {
367
+ this.parent.filters.remove(name)
368
+ }
369
+ this.parent.deleteField(name)
370
+ this.parent.sync.update(
371
+ 'properties.fields',
372
+ this.parent.properties.fields,
373
+ oldFields
374
+ )
375
+ this.parent.sync.commitBatch()
376
+ })
377
+ }
378
+ }
@@ -3,6 +3,7 @@ import { uMapAlert as Alert } from '../components/alerts/alert.js'
3
3
  import { MutatingForm } from './form/builder.js'
4
4
  import { translate } from './i18n.js'
5
5
  import * as Utils from './utils.js'
6
+ import * as DOMUtils from './domutils.js'
6
7
 
7
8
  // Dedicated object so we can deal with a separate dirty status, and thus
8
9
  // call the endpoint only when needed, saving one call at each save.
@@ -42,12 +43,42 @@ export class MapPermissions {
42
43
  }
43
44
 
44
45
  isAnonymousMap() {
45
- return !this._umap.properties.permissions.owner
46
+ return !this.properties.owner
47
+ }
48
+
49
+ isDraft() {
50
+ return this.properties.share_status === 0
51
+ }
52
+
53
+ userIsAuth() {
54
+ return Boolean(this._umap.properties.user?.id)
46
55
  }
47
56
 
48
57
  _editAnonymous(container) {
49
- const fields = []
50
58
  if (this.isOwner()) {
59
+ // We have a user, and this user has come through here, so they can edit the map, so let's allow to own the map.
60
+ // Note: real check is made on the back office anyway.
61
+ const template = `
62
+ <div class="anonymous soft-round aplat">
63
+ <h4><i class="icon icon-16 icon-anonymous"></i> ${translate('Anonymous map')}</h4>
64
+ <div data-ref="copiableInput"></div>
65
+ <p data-ref="p" hidden><button type="button" data-ref="button">${translate('Attach the map to my account')}</button></p>
66
+ </div>
67
+ `
68
+ const [root, { button, copiableInput, p }] = Utils.loadTemplateWithRefs(template)
69
+ container.appendChild(root)
70
+ if (this.properties.anonymous_edit_url) {
71
+ DOMUtils.copiableInput(
72
+ copiableInput,
73
+ translate('Secret edit link:'),
74
+ this.properties.anonymous_edit_url
75
+ )
76
+ }
77
+ if (this.userIsAuth()) {
78
+ button.addEventListener('click', () => this.attach())
79
+ p.hidden = false
80
+ }
81
+ const fields = []
51
82
  fields.push([
52
83
  'properties.edit_status',
53
84
  {
@@ -67,31 +98,6 @@ export class MapPermissions {
67
98
  const builder = new MutatingForm(this, fields)
68
99
  const form = builder.build()
69
100
  container.appendChild(form)
70
-
71
- if (this.properties.anonymous_edit_url) {
72
- DomUtil.createCopiableInput(
73
- container,
74
- translate('Secret edit link:'),
75
- this.properties.anonymous_edit_url
76
- )
77
- }
78
-
79
- if (this._umap.properties.user?.id) {
80
- // We have a user, and this user has come through here, so they can edit the map, so let's allow to own the map.
81
- // Note: real check is made on the back office anyway.
82
- const advancedActions = DomUtil.createFieldset(
83
- container,
84
- translate('Advanced actions')
85
- )
86
- const advancedButtons = DomUtil.create('div', 'button-bar', advancedActions)
87
- DomUtil.createButton(
88
- 'button',
89
- advancedButtons,
90
- translate('Attach the map to my account'),
91
- this.attach,
92
- this
93
- )
94
- }
95
101
  }
96
102
  }
97
103
 
@@ -187,6 +193,8 @@ export class MapPermissions {
187
193
  const [data, response, error] = await this._umap.server.post(this.getAttachUrl())
188
194
  if (!error) {
189
195
  this.properties.owner = this._umap.properties.user
196
+ this._umap.properties.user.is_owner = true
197
+ this.render()
190
198
  Alert.success(translate('Map has been attached to your account'))
191
199
  this._umap.editPanel.close()
192
200
  }
@@ -238,6 +246,10 @@ export class MapPermissions {
238
246
  )
239
247
  }
240
248
 
249
+ pull() {
250
+ this.setProperties(this._umap.properties.permissions)
251
+ }
252
+
241
253
  getShareStatusDisplay() {
242
254
  if (this._umap.properties.share_statuses) {
243
255
  return Object.fromEntries(this._umap.properties.share_statuses)[
@@ -245,10 +257,6 @@ export class MapPermissions {
245
257
  ]
246
258
  }
247
259
  }
248
-
249
- isDraft() {
250
- return this.properties.share_status === 0
251
- }
252
260
  }
253
261
 
254
262
  export class DataLayerPermissions {
@@ -177,7 +177,7 @@ export const CaptionControl = BaseButton.extend({
177
177
  position: 'topleft',
178
178
  className: 'umap-control-caption',
179
179
  title: translate('About'),
180
- icon: 'icon-caption',
180
+ icon: 'icon-info',
181
181
  },
182
182
 
183
183
  onClick: function () {
@@ -242,13 +242,13 @@ export const SearchControl = BaseButton.extend({
242
242
  const [container, { input, resultsContainer }] =
243
243
  Utils.loadTemplateWithRefs(template)
244
244
  const id = Math.random()
245
+ this.search = new U.Search(
246
+ this._umap._leafletMap,
247
+ input,
248
+ this.layer,
249
+ this.photonOptions
250
+ )
245
251
  this._umap.panel.open({ content: container }).then(() => {
246
- this.search = new U.Search(
247
- this._umap._leafletMap,
248
- input,
249
- this.layer,
250
- this.photonOptions
251
- )
252
252
  this.search.on('ajax:send', () => {
253
253
  this._umap.fire('dataloading', { id: id })
254
254
  })
@@ -1,12 +1,7 @@
1
- import {
2
- DivIcon,
3
- DomEvent,
4
- DomUtil,
5
- Icon,
6
- Point,
7
- } from '../../../vendors/leaflet/leaflet-src.esm.js'
1
+ import { DivIcon, Icon, Point } from '../../../vendors/leaflet/leaflet-src.esm.js'
8
2
  import { SCHEMA } from '../schema.js'
9
3
  import * as Utils from '../utils.js'
4
+ import * as DOMUtils from '../domutils.js'
10
5
 
11
6
  export function getClass(name) {
12
7
  switch (name) {
@@ -299,7 +294,7 @@ export function setContrast(icon, parent, src, bgcolor) {
299
294
  */
300
295
  if (!icon) return
301
296
 
302
- if (DomUtil.contrastedColor(parent, bgcolor)) {
297
+ if (DOMUtils.contrastedColor(parent, bgcolor)) {
303
298
  // Decide whether to switch svg to white or not, but do it
304
299
  // only for internal SVG, as invert could do weird things
305
300
  if (src.endsWith('.svg') && src !== SCHEMA.iconUrl.default) {
@@ -2,6 +2,7 @@ import colorbrewer from '../../../../vendors/colorbrewer/colorbrewer.js'
2
2
  import { DomUtil, FeatureGroup } from '../../../../vendors/leaflet/leaflet-src.esm.js'
3
3
  import { translate } from '../../i18n.js'
4
4
  import * as Utils from '../../utils.js'
5
+ import * as DOMUtils from '../../domutils.js'
5
6
  import { CircleMarker } from '../ui.js'
6
7
  import { LayerMixin } from './base.js'
7
8
 
@@ -76,15 +77,21 @@ const ClassifiedMixin = {
76
77
 
77
78
  renderLegend: function (container) {
78
79
  if (!this.datalayer.isLoaded()) return
79
- const parent = DomUtil.create('ul', '', container)
80
+ const ul = Utils.loadTemplate('<ul></ul>')
80
81
  const items = this.getLegendItems()
81
82
  for (const [color, label] of items) {
82
- const li = DomUtil.create('li', '', parent)
83
- const colorEl = DomUtil.create('span', 'datalayer-color', li)
84
- colorEl.style.backgroundColor = color
85
- const labelEl = DomUtil.create('span', '', li)
86
- labelEl.textContent = label
83
+ const rgbColor = DOMUtils.hexToRGB(color)
84
+ const opacity = this.datalayer.getOption('fillOpacity')
85
+ const bgColor = `rgba(${rgbColor.join(',')}, ${opacity})`
86
+ const li = Utils.loadTemplate(`
87
+ <li>
88
+ <span class="datalayer-color" style="background-color: ${bgColor};"></span>
89
+ <span>${label}</span>
90
+ </li>
91
+ `)
92
+ ul.appendChild(li)
87
93
  }
94
+ container.appendChild(ul)
88
95
  },
89
96
 
90
97
  getColorSchemes: function (classes) {
@@ -198,7 +205,7 @@ export const Choropleth = FeatureGroup.extend({
198
205
  'properties.choropleth.property',
199
206
  {
200
207
  handler: 'Select',
201
- selectOptions: this.datalayer.fieldKeys,
208
+ selectOptions: this.datalayer.fields.keys(),
202
209
  label: translate('Choropleth property value'),
203
210
  },
204
211
  ],
@@ -307,7 +314,7 @@ export const Circles = FeatureGroup.extend({
307
314
  'properties.circles.property',
308
315
  {
309
316
  handler: 'Select',
310
- selectOptions: this.datalayer.fieldKeys,
317
+ selectOptions: this.datalayer.fields.keys(),
311
318
  label: translate('Property name to compute circles'),
312
319
  },
313
320
  ],
@@ -384,7 +391,7 @@ export const Categorized = FeatureGroup.extend({
384
391
 
385
392
  _getValue: function (feature) {
386
393
  const key =
387
- this.datalayer.properties.categorized.property || this.datalayer.fieldKeys[0]
394
+ this.datalayer.properties.categorized.property || this.datalayer.fields.keys()[0]
388
395
  return feature.properties[key]
389
396
  },
390
397
 
@@ -437,7 +444,7 @@ export const Categorized = FeatureGroup.extend({
437
444
  'properties.categorized.property',
438
445
  {
439
446
  handler: 'Select',
440
- selectOptions: this.datalayer.fieldKeys,
447
+ selectOptions: this.datalayer.fields.keys(),
441
448
  label: translate('Category property'),
442
449
  },
443
450
  ],
@@ -5,11 +5,11 @@ import {
5
5
  Marker,
6
6
  Rectangle,
7
7
  Polyline,
8
- DomUtil,
9
8
  latLngBounds,
10
9
  } from '../../../../vendors/leaflet/leaflet-src.esm.js'
11
10
  import { translate } from '../../i18n.js'
12
11
  import * as Utils from '../../utils.js'
12
+ import * as DOMUtils from '../../domutils.js'
13
13
  import { Cluster as ClusterIcon } from '../icon.js'
14
14
  import { LayerMixin } from './base.js'
15
15
 
@@ -21,7 +21,7 @@ const MarkerCluster = Marker.extend({
21
21
  const bgColor = this.options.icon.options.color
22
22
  const textColor = this.options.icon.options.textColor
23
23
  counter.style.color =
24
- textColor || DomUtil.TextColorFromBackgroundColor(counter, bgColor)
24
+ textColor || DOMUtils.textColorFromBackgroundColor(counter, bgColor)
25
25
  },
26
26
 
27
27
  computeCoverage() {
@@ -281,6 +281,8 @@ export const Cluster = FeatureGroup.extend({
281
281
  ],
282
282
 
283
283
  onEdit: function (field, builder) {
284
- if (field === 'properties.cluster.radius') this.redraw()
284
+ if (field === 'properties.cluster.radius' || field === 'properties.color') {
285
+ this.redraw()
286
+ }
285
287
  },
286
288
  })
@@ -6,6 +6,7 @@ import {
6
6
  import { getLocale, translate } from '../i18n.js'
7
7
  import { Request } from '../request.js'
8
8
  import * as Utils from '../utils.js'
9
+ import * as DOMUtils from '../domutils.js'
9
10
  import * as Icon from './icon.js'
10
11
 
11
12
  export default async function loadTemplate(name, feature, container) {
@@ -171,7 +172,7 @@ class OSM extends PopupTemplate {
171
172
  const icon = Icon.makeElement(iconUrl, title)
172
173
  DomUtil.addClass(icon, 'icon')
173
174
  Icon.setContrast(icon, title, iconUrl, color)
174
- if (DomUtil.contrastedColor(title, color)) title.style.color = 'white'
175
+ if (DOMUtils.contrastedColor(title, color)) title.style.color = 'white'
175
176
  DomUtil.add('span', '', title, this.getName(feature))
176
177
  return title
177
178
  }
@@ -180,7 +181,7 @@ class OSM extends PopupTemplate {
180
181
  const props = feature.properties
181
182
  const locale = getLocale()
182
183
  if (locale && props[`name:${locale}`]) return props[`name:${locale}`]
183
- return props.name
184
+ return props.name || feature.getDisplayName()
184
185
  }
185
186
 
186
187
  async renderBody(feature) {
@@ -230,11 +231,31 @@ class OSM extends PopupTemplate {
230
231
  )
231
232
  )
232
233
  }
234
+ if (props.image) {
235
+ body.appendChild(
236
+ Utils.loadTemplate(`<div><img src="${props.image}" alt="" /></div>`)
237
+ )
238
+ }
239
+ if (props.mapillary) {
240
+ body.appendChild(
241
+ Utils.loadTemplate(
242
+ `<div><a href="https://www.mapillary.com/app/?focus=photo&pKey=${props.mapillary}" target="_blank">${translate('Mapillary')}<i class="icon icon-16 icon-external-link"></i></a></div>`
243
+ )
244
+ )
245
+ }
233
246
  const wikipedia = props[`wikipedia:${locale}`] || props.wikipedia
234
247
  if (wikipedia) {
235
248
  body.appendChild(
236
249
  Utils.loadTemplate(
237
- `<div class="wikipedia-link"><a href="https://wikipedia.org/wiki/${wikipedia}" target="_blank">${translate('Wikipedia')}</a></div>`
250
+ `<div><a href="https://wikipedia.org/wiki/${wikipedia}" target="_blank">${translate('Wikipedia')}<i class="icon icon-16 icon-external-link"></i></a></div>`
251
+ )
252
+ )
253
+ }
254
+ const wikidata = props[`wikidata:${locale}`] || props.wikidata
255
+ if (wikidata) {
256
+ body.appendChild(
257
+ Utils.loadTemplate(
258
+ `<div><a href="https://www.wikidata.org/wiki/${wikidata}" target="_blank">${translate('Wikidata')}<i class="icon icon-16 icon-external-link"></i></a></div>`
238
259
  )
239
260
  )
240
261
  }
@@ -242,7 +263,7 @@ class OSM extends PopupTemplate {
242
263
  if (id) {
243
264
  body.appendChild(
244
265
  Utils.loadTemplate(
245
- `<div class="osm-link"><a href="https://www.openstreetmap.org/${id}">${translate('See on OpenStreetMap')}</a></div>`
266
+ `<div class="osm-link"><a href="https://www.openstreetmap.org/${id}">${translate('See on OpenStreetMap')}<i class="icon icon-16 icon-external-link"></i></a></div>`
246
267
  )
247
268
  )
248
269
  }
@@ -257,7 +278,7 @@ class Wikipedia extends PopupTemplate {
257
278
  if (wikipedia && _WIKIPEDIA_CACHE[wikipedia]) return _WIKIPEDIA_CACHE[wikipedia]
258
279
  // Wikipedia value should be in form of "{locale}:{title}", according to https://wiki.openstreetmap.org/wiki/Key:wikipedia
259
280
  const [locale, page] = wikipedia.split(':')
260
- const url = `https://${locale}.wikipedia.org/w/api.php?action=query&format=json&origin=*&pithumbsize=500&prop=extracts|pageimages&titles=${page}`
281
+ const url = `https://${locale}.wikipedia.org/w/api.php?action=query&format=json&origin=*&pithumbsize=500&exintro=1&prop=extracts|pageimages&titles=${page}`
261
282
  const request = new Request()
262
283
  const response = await request.get(url)
263
284
  if (response?.ok) {
@@ -277,9 +298,21 @@ class Wikipedia extends PopupTemplate {
277
298
  const title = page.title || feature.getDisplayName()
278
299
  const extract = page.extract || ''
279
300
  const thumbnail = page.thumbnail?.source
280
- const [content, { image }] = Utils.loadTemplateWithRefs(
281
- `<div><h3>${Utils.escapeHTML(title)}</h3><img data-ref="image" hidden src="" />${Utils.escapeHTML(extract)}</div>`
282
- )
301
+ const [content, { image }] = Utils.loadTemplateWithRefs(`
302
+ <div>
303
+ <h3>${Utils.escapeHTML(title)}</h3>
304
+ <img data-ref="image" hidden src="" />
305
+ <p>
306
+ ${Utils.escapeHTML(extract)}
307
+ </p>
308
+ <p>
309
+ © ${translate('Wikipedia contributors')} •
310
+ <a href="https://wikipedia.org/wiki/${wikipedia}" target="_blank">
311
+ ${translate('See on Wikipedia')}
312
+ <i class="icon icon-16 icon-external-link"></i>
313
+ </a>
314
+ </p>
315
+ </div>`)
283
316
  if (thumbnail) {
284
317
  image.src = thumbnail
285
318
  image.hidden = false
@@ -349,6 +382,9 @@ class Route extends TitleMixin(PopupTemplate) {
349
382
  color: 'orange',
350
383
  }).addTo(map)
351
384
  })
385
+ if (feature.properties.description) {
386
+ root.appendChild(Utils.loadTemplate(`<p>${feature.properties.description}</p>`))
387
+ }
352
388
  return root
353
389
  }
354
390
  }