umap-project 2.3.1__py3-none-any.whl → 2.4.0b0__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 (210) hide show
  1. umap/.DS_Store +0 -0
  2. umap/__init__.py +1 -1
  3. umap/locale/en/LC_MESSAGES/django.po +81 -31
  4. umap/locale/fr/LC_MESSAGES/django.mo +0 -0
  5. umap/locale/fr/LC_MESSAGES/django.po +109 -59
  6. umap/management/commands/run_websocket_server.py +23 -0
  7. umap/models.py +6 -1
  8. umap/settings/base.py +11 -3
  9. umap/static/.DS_Store +0 -0
  10. umap/static/umap/.DS_Store +0 -0
  11. umap/static/umap/base.css +53 -162
  12. umap/static/umap/content.css +3 -2
  13. umap/static/umap/css/dialog.css +18 -0
  14. umap/static/umap/css/icon.css +8 -0
  15. umap/static/umap/css/importers.css +44 -0
  16. umap/static/umap/css/panel.css +19 -57
  17. umap/static/umap/css/tooltip.css +59 -0
  18. umap/static/umap/css/window.css +35 -0
  19. umap/static/umap/favicons/.DS_Store +0 -0
  20. umap/static/umap/fonts/.DS_Store +0 -0
  21. umap/static/umap/img/.DS_Store +0 -0
  22. umap/static/umap/img/alert-icon-error.svg +8 -0
  23. umap/static/umap/img/alert-icon-info.svg +4 -0
  24. umap/static/umap/img/alert-icon-success.svg +3 -0
  25. umap/static/umap/img/icon-external-link.svg +3 -0
  26. umap/static/umap/img/importers/communesfr.svg +5 -0
  27. umap/static/umap/img/importers/datasets.svg +13 -0
  28. umap/static/umap/img/importers/geodatamine.svg +10 -0
  29. umap/static/umap/img/importers/overpass.svg +7 -0
  30. umap/static/umap/img/importers/random.svg +18 -0
  31. umap/static/umap/img/importers/random1.svg +4 -0
  32. umap/static/umap/img/importers/random2.svg +4 -0
  33. umap/static/umap/img/source/.DS_Store +0 -0
  34. umap/static/umap/js/components/alerts/alert.css +160 -0
  35. umap/static/umap/js/components/alerts/alert.js +169 -0
  36. umap/static/umap/js/components/base.js +54 -0
  37. umap/static/umap/js/modules/autocomplete.js +347 -0
  38. umap/static/umap/js/modules/browser.js +1 -1
  39. umap/static/umap/js/modules/caption.js +4 -3
  40. umap/static/umap/js/modules/global.js +36 -12
  41. umap/static/umap/js/modules/help.js +255 -0
  42. umap/static/umap/js/modules/importer.js +280 -0
  43. umap/static/umap/js/modules/importers/communesfr.js +44 -0
  44. umap/static/umap/js/modules/importers/datasets.js +41 -0
  45. umap/static/umap/js/modules/importers/geodatamine.js +95 -0
  46. umap/static/umap/js/modules/importers/overpass.js +84 -0
  47. umap/static/umap/js/modules/request.js +12 -14
  48. umap/static/umap/js/modules/rules.js +241 -0
  49. umap/static/umap/js/modules/schema.js +63 -14
  50. umap/static/umap/js/modules/sync/engine.js +93 -0
  51. umap/static/umap/js/modules/sync/updaters.js +109 -0
  52. umap/static/umap/js/modules/sync/websocket.js +25 -0
  53. umap/static/umap/js/modules/ui/dialog.js +52 -0
  54. umap/static/umap/js/modules/{panel.js → ui/panel.js} +25 -14
  55. umap/static/umap/js/modules/ui/tooltip.js +116 -0
  56. umap/static/umap/js/modules/utils.js +25 -18
  57. umap/static/umap/js/umap.controls.js +13 -14
  58. umap/static/umap/js/umap.core.js +1 -324
  59. umap/static/umap/js/umap.features.js +67 -27
  60. umap/static/umap/js/umap.forms.js +9 -13
  61. umap/static/umap/js/umap.js +220 -180
  62. umap/static/umap/js/umap.layer.js +142 -74
  63. umap/static/umap/js/umap.permissions.js +5 -9
  64. umap/static/umap/js/umap.tableeditor.js +8 -8
  65. umap/static/umap/locale/am_ET.js +51 -16
  66. umap/static/umap/locale/am_ET.json +51 -16
  67. umap/static/umap/locale/ar.js +51 -16
  68. umap/static/umap/locale/ar.json +51 -16
  69. umap/static/umap/locale/ast.js +51 -16
  70. umap/static/umap/locale/ast.json +51 -16
  71. umap/static/umap/locale/bg.js +51 -16
  72. umap/static/umap/locale/bg.json +51 -16
  73. umap/static/umap/locale/br.js +55 -20
  74. umap/static/umap/locale/br.json +55 -20
  75. umap/static/umap/locale/ca.js +51 -16
  76. umap/static/umap/locale/ca.json +51 -16
  77. umap/static/umap/locale/cs_CZ.js +93 -58
  78. umap/static/umap/locale/cs_CZ.json +93 -58
  79. umap/static/umap/locale/da.js +51 -16
  80. umap/static/umap/locale/da.json +51 -16
  81. umap/static/umap/locale/de.js +56 -21
  82. umap/static/umap/locale/de.json +56 -21
  83. umap/static/umap/locale/el.js +51 -16
  84. umap/static/umap/locale/el.json +51 -16
  85. umap/static/umap/locale/en.js +51 -16
  86. umap/static/umap/locale/en.json +51 -16
  87. umap/static/umap/locale/en_US.json +51 -16
  88. umap/static/umap/locale/es.js +51 -16
  89. umap/static/umap/locale/es.json +51 -16
  90. umap/static/umap/locale/et.js +51 -16
  91. umap/static/umap/locale/et.json +51 -16
  92. umap/static/umap/locale/eu.js +51 -16
  93. umap/static/umap/locale/eu.json +51 -16
  94. umap/static/umap/locale/fa_IR.js +51 -16
  95. umap/static/umap/locale/fa_IR.json +51 -16
  96. umap/static/umap/locale/fi.js +51 -16
  97. umap/static/umap/locale/fi.json +51 -16
  98. umap/static/umap/locale/fr.js +52 -17
  99. umap/static/umap/locale/fr.json +52 -17
  100. umap/static/umap/locale/gl.js +51 -16
  101. umap/static/umap/locale/gl.json +51 -16
  102. umap/static/umap/locale/he.js +51 -16
  103. umap/static/umap/locale/he.json +51 -16
  104. umap/static/umap/locale/hr.js +51 -16
  105. umap/static/umap/locale/hr.json +51 -16
  106. umap/static/umap/locale/hu.js +51 -16
  107. umap/static/umap/locale/hu.json +51 -16
  108. umap/static/umap/locale/id.js +51 -16
  109. umap/static/umap/locale/id.json +51 -16
  110. umap/static/umap/locale/is.js +51 -16
  111. umap/static/umap/locale/is.json +51 -16
  112. umap/static/umap/locale/it.js +51 -16
  113. umap/static/umap/locale/it.json +51 -16
  114. umap/static/umap/locale/ja.js +51 -16
  115. umap/static/umap/locale/ja.json +51 -16
  116. umap/static/umap/locale/ko.js +51 -16
  117. umap/static/umap/locale/ko.json +51 -16
  118. umap/static/umap/locale/lt.js +51 -16
  119. umap/static/umap/locale/lt.json +51 -16
  120. umap/static/umap/locale/ms.js +51 -16
  121. umap/static/umap/locale/ms.json +51 -16
  122. umap/static/umap/locale/nl.js +51 -16
  123. umap/static/umap/locale/nl.json +51 -16
  124. umap/static/umap/locale/no.js +51 -16
  125. umap/static/umap/locale/no.json +51 -16
  126. umap/static/umap/locale/pl.js +93 -58
  127. umap/static/umap/locale/pl.json +93 -58
  128. umap/static/umap/locale/pl_PL.json +51 -16
  129. umap/static/umap/locale/pt.js +215 -180
  130. umap/static/umap/locale/pt.json +215 -180
  131. umap/static/umap/locale/pt_BR.js +51 -16
  132. umap/static/umap/locale/pt_BR.json +51 -16
  133. umap/static/umap/locale/pt_PT.js +51 -16
  134. umap/static/umap/locale/pt_PT.json +51 -16
  135. umap/static/umap/locale/ro.js +51 -16
  136. umap/static/umap/locale/ro.json +51 -16
  137. umap/static/umap/locale/ru.js +51 -16
  138. umap/static/umap/locale/ru.json +51 -16
  139. umap/static/umap/locale/si.js +51 -16
  140. umap/static/umap/locale/si.json +51 -16
  141. umap/static/umap/locale/sk_SK.js +51 -16
  142. umap/static/umap/locale/sk_SK.json +51 -16
  143. umap/static/umap/locale/sl.js +51 -16
  144. umap/static/umap/locale/sl.json +51 -16
  145. umap/static/umap/locale/sr.js +51 -16
  146. umap/static/umap/locale/sr.json +51 -16
  147. umap/static/umap/locale/sv.js +51 -16
  148. umap/static/umap/locale/sv.json +51 -16
  149. umap/static/umap/locale/th_TH.js +51 -16
  150. umap/static/umap/locale/th_TH.json +51 -16
  151. umap/static/umap/locale/tr.js +51 -16
  152. umap/static/umap/locale/tr.json +51 -16
  153. umap/static/umap/locale/uk_UA.js +51 -16
  154. umap/static/umap/locale/uk_UA.json +51 -16
  155. umap/static/umap/locale/vi.js +51 -16
  156. umap/static/umap/locale/vi.json +51 -16
  157. umap/static/umap/locale/vi_VN.json +51 -16
  158. umap/static/umap/locale/zh.js +51 -16
  159. umap/static/umap/locale/zh.json +51 -16
  160. umap/static/umap/locale/zh_CN.json +51 -16
  161. umap/static/umap/locale/zh_TW.Big5.json +51 -16
  162. umap/static/umap/locale/zh_TW.js +51 -16
  163. umap/static/umap/locale/zh_TW.json +51 -16
  164. umap/static/umap/map.css +27 -41
  165. umap/static/umap/unittests/sync.js +105 -0
  166. umap/static/umap/unittests/utils.js +76 -34
  167. umap/static/umap/vars.css +18 -1
  168. umap/static/umap/vendors/dompurify/purify.es.js +5 -59
  169. umap/static/umap/vendors/dompurify/purify.es.mjs.map +1 -1
  170. umap/templates/umap/components/alerts/alert.html +89 -0
  171. umap/templates/umap/content.html +4 -3
  172. umap/templates/umap/css.html +4 -0
  173. umap/templates/umap/home.html +3 -0
  174. umap/templates/umap/js.html +0 -3
  175. umap/templates/umap/map_init.html +2 -8
  176. umap/templates/umap/messages.html +9 -11
  177. umap/templates/umap/search.html +3 -0
  178. umap/tests/.DS_Store +0 -0
  179. umap/tests/base.py +2 -0
  180. umap/tests/integration/.DS_Store +0 -0
  181. umap/tests/integration/conftest.py +30 -0
  182. umap/tests/integration/test_anonymous_owned_map.py +8 -13
  183. umap/tests/integration/test_browser.py +1 -1
  184. umap/tests/integration/test_conditional_rules.py +201 -0
  185. umap/tests/integration/test_dashboard.py +1 -1
  186. umap/tests/integration/test_datalayer.py +2 -3
  187. umap/tests/integration/test_edit_datalayer.py +4 -4
  188. umap/tests/integration/test_edit_map.py +1 -1
  189. umap/tests/integration/test_facets_browser.py +3 -3
  190. umap/tests/integration/test_import.py +138 -49
  191. umap/tests/integration/test_map.py +2 -2
  192. umap/tests/integration/{test_collaborative_editing.py → test_optimistic_merge.py} +7 -7
  193. umap/tests/integration/test_owned_map.py +1 -1
  194. umap/tests/integration/test_picto.py +2 -2
  195. umap/tests/integration/test_statics.py +1 -1
  196. umap/tests/integration/test_websocket_sync.py +283 -0
  197. umap/tests/settings.py +5 -0
  198. umap/tests/test_datalayer_views.py +0 -1
  199. umap/tests/test_views.py +53 -0
  200. umap/urls.py +5 -0
  201. umap/views.py +40 -11
  202. umap/websocket_server.py +92 -0
  203. {umap_project-2.3.1.dist-info → umap_project-2.4.0b0.dist-info}/METADATA +11 -9
  204. {umap_project-2.3.1.dist-info → umap_project-2.4.0b0.dist-info}/RECORD +207 -164
  205. {umap_project-2.3.1.dist-info → umap_project-2.4.0b0.dist-info}/WHEEL +1 -1
  206. umap/static/umap/js/umap.autocomplete.js +0 -341
  207. umap/static/umap/js/umap.importer.js +0 -187
  208. umap/static/umap/js/umap.ui.js +0 -190
  209. {umap_project-2.3.1.dist-info → umap_project-2.4.0b0.dist-info}/entry_points.txt +0 -0
  210. {umap_project-2.3.1.dist-info → umap_project-2.4.0b0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,280 @@
1
+ import { DomUtil, DomEvent } from '../../vendors/leaflet/leaflet-src.esm.js'
2
+ import { translate } from './i18n.js'
3
+ import { uMapAlert as Alert } from '../components/alerts/alert.js'
4
+ import Dialog from './ui/dialog.js'
5
+ import { SCHEMA } from './schema.js'
6
+ import * as Utils from './utils.js'
7
+
8
+ const TEMPLATE = `
9
+ <h3><i class="icon icon-16 icon-upload"></i><span>${translate('Import data')}</span></h3>
10
+ <fieldset class="formbox">
11
+ <legend class="counter">${translate('Choose data')}</legend>
12
+ <input type="file" multiple autofocus onchange />
13
+ <input type="url" placeholder="${translate('Provide an URL here')}" onchange />
14
+ <textarea onchange placeholder="${translate('Paste your data here')}"></textarea>
15
+ <div class="importers">
16
+ <h4>${translate('Import helpers:')}</h4>
17
+ <ul class="grid-container">
18
+ </ul>
19
+ </div>
20
+ </fieldset>
21
+ <fieldset class="formbox">
22
+ <legend class="counter" data-help="importFormats">${translate('Choose the format')}</legend>
23
+ <select name="format" onchange></select>
24
+ </fieldset>
25
+ <fieldset id="destination" class="formbox">
26
+ <legend class="counter">${translate('Choose the layer')}</legend>
27
+ <select name="layer-id" onchange></select>
28
+ <label id="clear">
29
+ <input type="checkbox" name="clear" />
30
+ ${translate('Replace layer content')}
31
+ </label>
32
+ <input type="text" name="layer-name" placeholder="${translate('Layer name')}" />
33
+ </fieldset>
34
+ <fieldset id="import-mode" class="formbox">
35
+ <legend class="counter" data-help="importMode">${translate('Choose import mode')}</legend>
36
+ <label>
37
+ <input type="radio" name="action" value="copy" />
38
+ ${translate('Copy into the layer')}
39
+ </label>
40
+ <label>
41
+ <input type="radio" name="action" value="link" />
42
+ ${translate('Link to the layer as remote data')}
43
+ </label>
44
+ </fieldset>
45
+ <input type="button" class="button" name="submit" value="${translate('Import data')}" />
46
+ `
47
+
48
+ export default class Importer {
49
+ constructor(map) {
50
+ this.map = map
51
+ this.TYPES = ['geojson', 'csv', 'gpx', 'kml', 'osm', 'georss', 'umap']
52
+ this.IMPORTERS = []
53
+ this.loadImporters()
54
+ this.dialog = new Dialog(this.map._controlContainer)
55
+ }
56
+
57
+ loadImporters() {
58
+ for (const key of Object.keys(this.map.options.importers || {})) {
59
+ import(`./importers/${key}.js`).then((mod) => {
60
+ this.IMPORTERS.push(new mod.Importer(this.map, this.map.options.importers[key]))
61
+ })
62
+ }
63
+ }
64
+
65
+ qs(query) {
66
+ return this.container.querySelector(query)
67
+ }
68
+
69
+ get url() {
70
+ return this.qs('[type=url]').value
71
+ }
72
+
73
+ set url(value) {
74
+ this.qs('[type=url]').value = value
75
+ this.onChange()
76
+ }
77
+
78
+ get format() {
79
+ return this.qs('[name=format]').value
80
+ }
81
+
82
+ set format(value) {
83
+ this.qs('[name=format]').value = value
84
+ this.onChange()
85
+ }
86
+
87
+ get files() {
88
+ return this.qs('[type=file]').files
89
+ }
90
+
91
+ get raw() {
92
+ return this.qs('textarea').value
93
+ }
94
+
95
+ get clear() {
96
+ return Boolean(this.qs('[name=clear]').checked)
97
+ }
98
+
99
+ get action() {
100
+ return this.qs('[name=action]:checked').value
101
+ }
102
+
103
+ get layerId() {
104
+ return this.qs('[name=layer-id]').value
105
+ }
106
+
107
+ set layerId(value) {
108
+ this.qs('[name=layer-id]').value = value
109
+ }
110
+
111
+ get layerName() {
112
+ return this.qs('[name=layer-name]').value
113
+ }
114
+
115
+ set layerName(name) {
116
+ this.qs('[name=layer-name]').value = name
117
+ this.onChange()
118
+ }
119
+
120
+ get layer() {
121
+ return (
122
+ this.map.datalayers[this.layerId] ||
123
+ this.map.createDataLayer({ name: this.layerName })
124
+ )
125
+ }
126
+
127
+ build() {
128
+ this.container = DomUtil.create('div', 'umap-upload')
129
+ this.container.innerHTML = TEMPLATE
130
+ if (this.IMPORTERS.length) {
131
+ const parent = this.container.querySelector('.importers ul')
132
+ for (const plugin of this.IMPORTERS.sort((a, b) => (a.id > b.id ? 1 : -1))) {
133
+ L.DomUtil.createButton(
134
+ plugin.id,
135
+ DomUtil.element({tagName: 'li', parent}),
136
+ plugin.name,
137
+ () => plugin.open(this)
138
+ )
139
+ }
140
+ this.qs('.importers').toggleAttribute('hidden', false)
141
+ }
142
+ for (const type of this.TYPES) {
143
+ DomUtil.element({
144
+ tagName: 'option',
145
+ parent: this.qs('[name=format]'),
146
+ value: type,
147
+ textContent: type,
148
+ })
149
+ }
150
+ this.map.help.parse(this.container)
151
+ DomEvent.on(this.qs('[name=submit]'), 'click', this.submit, this)
152
+ DomEvent.on(this.qs('[type=file]'), 'change', this.onFileChange, this)
153
+ for (const element of this.container.querySelectorAll('[onchange]')) {
154
+ DomEvent.on(element, 'change', this.onChange, this)
155
+ }
156
+ }
157
+
158
+ onChange() {
159
+ this.qs('#destination').toggleAttribute('hidden', this.format === 'umap')
160
+ this.qs('#import-mode').toggleAttribute(
161
+ 'hidden',
162
+ this.format === 'umap' || !this.url
163
+ )
164
+ this.qs('[name=layer-name]').toggleAttribute('hidden', Boolean(this.layerId))
165
+ this.qs('#clear').toggleAttribute('hidden', !Boolean(this.layerId))
166
+ }
167
+
168
+ onFileChange(e) {
169
+ let type = '',
170
+ newType
171
+ for (const file of e.target.files) {
172
+ newType = U.Utils.detectFileType(file)
173
+ if (!type && newType) type = newType
174
+ if (type && newType !== type) {
175
+ type = ''
176
+ break
177
+ }
178
+ }
179
+ this.format = type
180
+ }
181
+
182
+ onLoad() {
183
+ this.qs('[type=file]').value = null
184
+ this.url = null
185
+ this.format = undefined
186
+ this.layerName = null
187
+ const layerSelect = this.qs('[name="layer-id"]')
188
+ layerSelect.innerHTML = ''
189
+ this.map.eachDataLayerReverse((datalayer) => {
190
+ if (datalayer.isLoaded() && !datalayer.isRemoteLayer()) {
191
+ DomUtil.element({
192
+ tagName: 'option',
193
+ parent: layerSelect,
194
+ textContent: datalayer.options.name,
195
+ value: L.stamp(datalayer),
196
+ })
197
+ }
198
+ })
199
+ DomUtil.element({
200
+ tagName: 'option',
201
+ value: '',
202
+ textContent: translate('Import in a new layer'),
203
+ parent: layerSelect,
204
+ selected: true,
205
+ })
206
+ }
207
+
208
+ open() {
209
+ if (!this.container) this.build()
210
+ const onLoad = this.map.editPanel.open({ content: this.container })
211
+ onLoad.then(() => this.onLoad())
212
+ }
213
+
214
+ openFiles() {
215
+ this.open()
216
+ this.fileInput.showPicker()
217
+ }
218
+
219
+ submit() {
220
+ if (this.format === 'umap') this.full()
221
+ else if (!this.url) this.copy()
222
+ else if (this.action) this[this.action]()
223
+ }
224
+
225
+ full() {
226
+ this.map.once('postsync', this.map._setDefaultCenter)
227
+ try {
228
+ if (this.files.length) {
229
+ for (const file of this.files) {
230
+ this.map.processFileToImport(file, null, 'umap')
231
+ }
232
+ } else if (this.raw) {
233
+ this.map.importRaw(this.raw)
234
+ } else if (this.url) {
235
+ this.map.importFromUrl(this.url, this.format)
236
+ }
237
+ } catch (e) {
238
+ Alert.error(translate('Invalid umap data'))
239
+ console.error(e)
240
+ }
241
+ }
242
+
243
+ link() {
244
+ if (!this.url) return
245
+ if (!this.format) {
246
+ Alert.error(translate('Please choose a format'))
247
+ return
248
+ }
249
+ let layer = this.layer
250
+ layer.options.remoteData = {
251
+ url: this.url,
252
+ format: this.format,
253
+ }
254
+ if (this.map.options.urls.ajax_proxy) {
255
+ layer.options.remoteData.proxy = true
256
+ layer.options.remoteData.ttl = SCHEMA.ttl.default
257
+ }
258
+ layer.fetchRemoteData(true)
259
+ }
260
+
261
+ copy() {
262
+ // Format may be guessed from file later.
263
+ // Usefull in case of multiple files with different formats.
264
+ if (!this.format && !this.files.length) {
265
+ Alert.error(translate('Please choose a format'))
266
+ return
267
+ }
268
+ let layer = this.layer
269
+ if (this.clear) layer.empty()
270
+ if (this.files.length) {
271
+ for (const file of this.files) {
272
+ this.map.processFileToImport(file, layer, this.format)
273
+ }
274
+ } else if (this.raw) {
275
+ layer.importRaw(this.raw, this.format)
276
+ } else if (this.url) {
277
+ layer.importFromUrl(this.url, this.format)
278
+ }
279
+ }
280
+ }
@@ -0,0 +1,44 @@
1
+ import { DomUtil } from '../../../vendors/leaflet/leaflet-src.esm.js'
2
+ import { BaseAjax, SingleMixin } from '../autocomplete.js'
3
+
4
+ class Autocomplete extends SingleMixin(BaseAjax) {
5
+ createResult(item) {
6
+ return super.createResult({
7
+ value: item.code,
8
+ label: `${item.nom} (${item.code})`,
9
+ })
10
+ }
11
+ }
12
+
13
+ export class Importer {
14
+ constructor(map, options) {
15
+ this.name = options.name || 'Communes'
16
+ this.id = 'communesfr'
17
+ }
18
+
19
+ async open(importer) {
20
+ const container = DomUtil.create('div')
21
+ DomUtil.createTitle(container, this.name)
22
+ DomUtil.element({
23
+ tagName: 'p',
24
+ parent: container,
25
+ textContent: "Importer les contours d'une commune française.",
26
+ })
27
+ const options = {
28
+ placeholder: 'Commune…',
29
+ url: 'https://geo.api.gouv.fr/communes?nom={q}&limit=5',
30
+ on_select: (choice) => {
31
+ importer.url = `https://geo.api.gouv.fr/communes?code=${choice.item.value}&format=geojson&geometry=contour`
32
+ importer.format = 'geojson'
33
+ importer.layerName = choice.item.label
34
+ importer.dialog.close()
35
+ },
36
+ }
37
+ this.autocomplete = new Autocomplete(container, options)
38
+
39
+ importer.dialog.open({
40
+ content: container,
41
+ className: `${this.id} importer dark`,
42
+ })
43
+ }
44
+ }
@@ -0,0 +1,41 @@
1
+ import { DomUtil } from '../../../vendors/leaflet/leaflet-src.esm.js'
2
+ import { translate } from '../i18n.js'
3
+
4
+ export class Importer {
5
+ constructor(map, options) {
6
+ this.name = options.name || 'Datasets'
7
+ this.choices = options?.choices
8
+ this.id = 'datasets'
9
+ }
10
+
11
+ async open(importer) {
12
+ const container = DomUtil.create('div', 'formbox')
13
+ DomUtil.element({ tagName: 'h3', textContent: this.name, parent: container })
14
+ const select = DomUtil.create('select', '', container)
15
+ const noPreset = DomUtil.element({
16
+ tagName: 'option',
17
+ parent: select,
18
+ value: '',
19
+ textContent: translate('Choose a dataset'),
20
+ })
21
+ for (const dataset of this.choices) {
22
+ const option = DomUtil.create('option', '', select)
23
+ option.value = dataset.url
24
+ option.textContent = dataset.label
25
+ option.dataset.format = dataset.format || 'geojson'
26
+ }
27
+ const confirm = () => {
28
+ if (select.value) {
29
+ importer.url = select.value
30
+ importer.format = select.options[select.selectedIndex].dataset.format
31
+ }
32
+ importer.dialog.close()
33
+ }
34
+ L.DomUtil.createButton('', container, translate('Choose this dataset'), confirm)
35
+
36
+ importer.dialog.open({
37
+ content: container,
38
+ className: `${this.id} importer dark`,
39
+ })
40
+ }
41
+ }
@@ -0,0 +1,95 @@
1
+ import { DomUtil, DomEvent } from '../../../vendors/leaflet/leaflet-src.esm.js'
2
+ import { BaseAjax, SingleMixin } from '../autocomplete.js'
3
+ import { translate } from '../i18n.js'
4
+ import * as Utils from '../utils.js'
5
+ import { uMapAlert as Alert } from '../../components/alerts/alert.js'
6
+
7
+ const BOUNDARY_TYPES = {
8
+ admin_6: 'département',
9
+ admin_7: 'pays (loi Voynet)',
10
+ admin_8: 'commune',
11
+ admin_9: 'quartier, hameau, arrondissement',
12
+ political: 'canton',
13
+ local_authority: 'EPCI',
14
+ }
15
+
16
+ const TEMPLATE = `
17
+ <h3>GeoDataMine</h3>
18
+ <p>${translate('GeoDataMine: thematic data from OpenStreetMap')}.</p>
19
+ <select name="theme">
20
+ <option value="">${translate('Choose a theme')}</option>
21
+ </select>
22
+ <label>
23
+ <input type="checkbox" name="aspoint" />
24
+ ${translate('Symplify all geometries to points')}
25
+ </label>
26
+ <label id="boundary">
27
+ </label>
28
+ <button class="button">${translate('Choose this data')}</button>
29
+ `
30
+
31
+ class Autocomplete extends SingleMixin(BaseAjax) {
32
+ createResult(item) {
33
+ return super.createResult({
34
+ value: item.id,
35
+ label: `${item.name} (${BOUNDARY_TYPES[item.type]} — ${item.ref})`,
36
+ })
37
+ }
38
+ }
39
+
40
+ export class Importer {
41
+ constructor(map, options = {}) {
42
+ this.map = map
43
+ this.name = options.name || 'GeoDataMine'
44
+ this.baseUrl = options?.url || 'https://geodatamine.fr'
45
+ this.id = 'geodatamine'
46
+ }
47
+
48
+ async open(importer) {
49
+ let boundary = null
50
+ let boundaryName = null
51
+ const container = DomUtil.create('div')
52
+ container.innerHTML = TEMPLATE
53
+ const response = await importer.map.request.get(`${this.baseUrl}/themes`)
54
+ const select = container.querySelector('select')
55
+ if (response && response.ok) {
56
+ const { themes } = await response.json()
57
+ themes.sort((a, b) => Utils.naturalSort(a['name:fr'], b ['name:fr']))
58
+ for (const theme of themes) {
59
+ DomUtil.element({
60
+ tagName: 'option',
61
+ value: theme.id,
62
+ textContent: theme['name:fr'],
63
+ parent: select,
64
+ })
65
+ }
66
+ } else {
67
+ console.error(response)
68
+ }
69
+ const asPoint = container.querySelector('[name=aspoint]')
70
+ this.autocomplete = new Autocomplete(container.querySelector('#boundary'), {
71
+ placeholder: translate('Search admin boundary'),
72
+ url: `${this.baseUrl}/boundaries/search?text={q}`,
73
+ on_select: (choice) => {
74
+ boundary = choice.item.value
75
+ boundaryName = choice.item.label
76
+ },
77
+ })
78
+ const confirm = () => {
79
+ if (!boundary || !select.value) {
80
+ Alert.error(translate('Please choose a theme and a boundary first.'))
81
+ return
82
+ }
83
+ importer.url = `${this.baseUrl}/data/${select.value}/${boundary}?format=geojson&aspoint=${asPoint.checked}`
84
+ importer.format = 'geojson'
85
+ importer.layerName = `${boundaryName} — ${select.options[select.selectedIndex].textContent}`
86
+ importer.dialog.close()
87
+ }
88
+ DomEvent.on(container.querySelector('button'), 'click', confirm)
89
+
90
+ importer.dialog.open({
91
+ content: container,
92
+ className: `${this.id} importer dark`,
93
+ })
94
+ }
95
+ }
@@ -0,0 +1,84 @@
1
+ import { DomUtil } from '../../../vendors/leaflet/leaflet-src.esm.js'
2
+ import { BaseAjax, SingleMixin } from '../autocomplete.js'
3
+ import { translate } from '../i18n.js'
4
+ import { uMapAlert as Alert } from '../../components/alerts/alert.js'
5
+
6
+ const TEMPLATE = `
7
+ <h3>Overpass</h3>
8
+ <label>
9
+ <span data-help="overpassImporter">${translate('Expression')}</span>
10
+ <input type="text" placeholder="amenity=drinking_water" name="tags" />
11
+ </label>
12
+ <label>
13
+ ${translate('Geometry mode')}
14
+ <select name="out-mode">
15
+ <option value="geom" selected>${translate('Default')}</option>
16
+ <option value="center">${translate('Only geometry centers')}</option>
17
+ </select>
18
+ </label>
19
+ <label id="area"><span>${translate('Search area')}</span></label>
20
+ `
21
+
22
+ class Autocomplete extends SingleMixin(BaseAjax) {
23
+ handleResults(data) {
24
+ return super.handleResults(data.features)
25
+ }
26
+
27
+ createResult(item) {
28
+ return super.createResult({
29
+ // Overpass convention to get their id from an osm one.
30
+ value: item.properties.osm_id + 3600000000,
31
+ label: `${item.properties.name}`,
32
+ })
33
+ }
34
+ }
35
+
36
+ export class Importer {
37
+ constructor(map, options) {
38
+ this.map = map
39
+ this.name = options.name || 'Overpass'
40
+ this.baseUrl = options?.url || 'https://overpass-api.de/api/interpreter'
41
+ this.id = 'overpass'
42
+ }
43
+
44
+ async open(importer) {
45
+ let boundary = null
46
+ let boundaryName = null
47
+ const container = DomUtil.create('div')
48
+ container.innerHTML = TEMPLATE
49
+ this.autocomplete = new Autocomplete(container.querySelector('#area'), {
50
+ url: 'https://photon.komoot.io/api?q={q}&osm_tag=place',
51
+ placeholder: translate(
52
+ 'Type area name, or let empty to load data in current map view'
53
+ ),
54
+ on_select: (choice) => {
55
+ boundary = choice.item.value
56
+ boundaryName = choice.item.label
57
+ },
58
+ })
59
+ this.map.help.parse(container)
60
+
61
+ const confirm = () => {
62
+ let tags = container.querySelector('[name=tags]').value
63
+ if (!tags) {
64
+ Alert.error(translate('Please define an expression for the query first'))
65
+ return
66
+ }
67
+ const outMode = container.querySelector('[name=out-mode]').value
68
+ if (!tags.startsWith('[')) tags = `[${tags}]`
69
+ let area = '{south},{west},{north},{east}'
70
+ if (boundary) area = `area:${boundary}`
71
+ let query = `[out:json];nwr${tags}(${area});out ${outMode};`
72
+ importer.url = `${this.baseUrl}?data=${query}`
73
+ if (boundary) importer.layerName = boundaryName
74
+ importer.format = 'osm'
75
+ importer.dialog.close()
76
+ }
77
+ L.DomUtil.createButton('', container, translate('Choose this data'), confirm)
78
+
79
+ importer.dialog.open({
80
+ content: container,
81
+ className: `${this.id} importer dark`,
82
+ })
83
+ }
84
+ }
@@ -1,5 +1,5 @@
1
- // Uses `L._`` from Leaflet.i18n which we cannot import as a module yet
2
- import { DomUtil } from '../../vendors/leaflet/leaflet-src.esm.js'
1
+ import { translate } from './i18n.js'
2
+ import { uMapAlert as Alert } from '../components/alerts/alert.js'
3
3
 
4
4
  export class RequestError extends Error {}
5
5
 
@@ -47,14 +47,13 @@ class BaseRequest {
47
47
  // In case of error, an alert is sent, but non 20X status are not handled
48
48
  // The consumer must check the response status by hand
49
49
  export class Request extends BaseRequest {
50
- constructor(ui) {
51
- super()
52
- this.ui = ui
50
+ fire(name, params) {
51
+ document.body.dispatchEvent(new CustomEvent(name, params))
53
52
  }
54
53
 
55
54
  async _fetch(method, uri, headers, data) {
56
55
  const id = Math.random()
57
- this.ui.fire('dataloading', { id: id })
56
+ this.fire('dataloading', { id: id })
58
57
  try {
59
58
  const response = await BaseRequest.prototype._fetch.call(
60
59
  this,
@@ -68,7 +67,7 @@ export class Request extends BaseRequest {
68
67
  if (error instanceof NOKError) return this._onNOK(error)
69
68
  return this._onError(error)
70
69
  } finally {
71
- this.ui.fire('dataload', { id: id })
70
+ this.fire('dataload', { id: id })
72
71
  }
73
72
  }
74
73
 
@@ -81,7 +80,7 @@ export class Request extends BaseRequest {
81
80
  }
82
81
 
83
82
  _onError(error) {
84
- this.ui.alert({ content: L._('Problem in the response'), level: 'error' })
83
+ Alert.error(translate('Problem in the response'))
85
84
  }
86
85
 
87
86
  _onNOK(error) {
@@ -127,9 +126,9 @@ export class ServerRequest extends Request {
127
126
  try {
128
127
  const data = await response.json()
129
128
  if (data.info) {
130
- this.ui.alert({ content: data.info, level: 'info' })
129
+ Alert.info(data.info)
131
130
  } else if (data.error) {
132
- this.ui.alert({ content: data.error, level: 'error' })
131
+ Alert.error(data.error)
133
132
  return this._onError(new Error(data.error))
134
133
  }
135
134
  return [data, response, null]
@@ -144,10 +143,9 @@ export class ServerRequest extends Request {
144
143
 
145
144
  _onNOK(error) {
146
145
  if (error.status === 403) {
147
- this.ui.alert({
148
- content: error.message || L._('Action not allowed :('),
149
- level: 'error',
150
- })
146
+ Alert.error(error.message || translate('Action not allowed :('))
147
+ } else {
148
+ super._onError(error)
151
149
  }
152
150
  return [{}, error.response, error]
153
151
  }