umap-project 2.3.1__py3-none-any.whl → 2.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of umap-project might be problematic. Click here for more details.
- umap/__init__.py +1 -1
- umap/locale/en/LC_MESSAGES/django.po +81 -31
- umap/locale/fr/LC_MESSAGES/django.mo +0 -0
- umap/locale/fr/LC_MESSAGES/django.po +117 -66
- umap/management/commands/run_websocket_server.py +23 -0
- umap/models.py +6 -1
- umap/settings/base.py +11 -3
- umap/static/umap/base.css +64 -184
- umap/static/umap/content.css +3 -2
- umap/static/umap/css/dialog.css +18 -0
- umap/static/umap/css/icon.css +8 -0
- umap/static/umap/css/importers.css +51 -0
- umap/static/umap/css/panel.css +18 -57
- umap/static/umap/css/tooltip.css +59 -0
- umap/static/umap/css/window.css +35 -0
- umap/static/umap/img/16-white.svg +1 -3
- umap/static/umap/img/alert-icon-error.svg +8 -0
- umap/static/umap/img/alert-icon-info.svg +4 -0
- umap/static/umap/img/alert-icon-success.svg +3 -0
- umap/static/umap/img/icon-external-link.svg +3 -0
- umap/static/umap/img/importers/communesfr.svg +5 -0
- umap/static/umap/img/importers/datasets.svg +13 -0
- umap/static/umap/img/importers/geodatamine.svg +10 -0
- umap/static/umap/img/importers/overpass.svg +7 -0
- umap/static/umap/img/importers/random.svg +18 -0
- umap/static/umap/img/importers/random1.svg +4 -0
- umap/static/umap/img/importers/random2.svg +4 -0
- umap/static/umap/img/source/16-white.svg +2 -4
- umap/static/umap/js/components/alerts/alert.css +160 -0
- umap/static/umap/js/components/alerts/alert.js +169 -0
- umap/static/umap/js/components/base.js +54 -0
- umap/static/umap/js/modules/autocomplete.js +347 -0
- umap/static/umap/js/modules/browser.js +6 -6
- umap/static/umap/js/modules/caption.js +5 -4
- umap/static/umap/js/modules/global.js +36 -12
- umap/static/umap/js/modules/help.js +255 -0
- umap/static/umap/js/modules/importer.js +308 -0
- umap/static/umap/js/modules/importers/communesfr.js +44 -0
- umap/static/umap/js/modules/importers/datasets.js +42 -0
- umap/static/umap/js/modules/importers/geodatamine.js +95 -0
- umap/static/umap/js/modules/importers/overpass.js +84 -0
- umap/static/umap/js/modules/request.js +12 -14
- umap/static/umap/js/modules/rules.js +241 -0
- umap/static/umap/js/modules/schema.js +63 -14
- umap/static/umap/js/modules/sync/engine.js +93 -0
- umap/static/umap/js/modules/sync/updaters.js +109 -0
- umap/static/umap/js/modules/sync/websocket.js +25 -0
- umap/static/umap/js/modules/ui/dialog.js +52 -0
- umap/static/umap/js/modules/{panel.js → ui/panel.js} +25 -14
- umap/static/umap/js/modules/ui/tooltip.js +116 -0
- umap/static/umap/js/modules/utils.js +25 -18
- umap/static/umap/js/umap.controls.js +13 -14
- umap/static/umap/js/umap.core.js +1 -324
- umap/static/umap/js/umap.features.js +77 -29
- umap/static/umap/js/umap.forms.js +9 -13
- umap/static/umap/js/umap.js +254 -215
- umap/static/umap/js/umap.layer.js +152 -74
- umap/static/umap/js/umap.permissions.js +5 -9
- umap/static/umap/js/umap.popup.js +1 -1
- umap/static/umap/js/umap.tableeditor.js +8 -8
- umap/static/umap/locale/am_ET.js +51 -16
- umap/static/umap/locale/am_ET.json +51 -16
- umap/static/umap/locale/ar.js +51 -16
- umap/static/umap/locale/ar.json +51 -16
- umap/static/umap/locale/ast.js +51 -16
- umap/static/umap/locale/ast.json +51 -16
- umap/static/umap/locale/bg.js +51 -16
- umap/static/umap/locale/bg.json +51 -16
- umap/static/umap/locale/br.js +55 -20
- umap/static/umap/locale/br.json +55 -20
- umap/static/umap/locale/ca.js +51 -16
- umap/static/umap/locale/ca.json +51 -16
- umap/static/umap/locale/cs_CZ.js +93 -58
- umap/static/umap/locale/cs_CZ.json +93 -58
- umap/static/umap/locale/da.js +51 -16
- umap/static/umap/locale/da.json +51 -16
- umap/static/umap/locale/de.js +56 -21
- umap/static/umap/locale/de.json +56 -21
- umap/static/umap/locale/el.js +51 -16
- umap/static/umap/locale/el.json +51 -16
- umap/static/umap/locale/en.js +52 -16
- umap/static/umap/locale/en.json +52 -16
- umap/static/umap/locale/en_US.json +51 -16
- umap/static/umap/locale/es.js +51 -16
- umap/static/umap/locale/es.json +51 -16
- umap/static/umap/locale/et.js +51 -16
- umap/static/umap/locale/et.json +51 -16
- umap/static/umap/locale/eu.js +51 -16
- umap/static/umap/locale/eu.json +51 -16
- umap/static/umap/locale/fa_IR.js +51 -16
- umap/static/umap/locale/fa_IR.json +51 -16
- umap/static/umap/locale/fi.js +51 -16
- umap/static/umap/locale/fi.json +51 -16
- umap/static/umap/locale/fr.js +61 -25
- umap/static/umap/locale/fr.json +61 -25
- umap/static/umap/locale/gl.js +51 -16
- umap/static/umap/locale/gl.json +51 -16
- umap/static/umap/locale/he.js +51 -16
- umap/static/umap/locale/he.json +51 -16
- umap/static/umap/locale/hr.js +51 -16
- umap/static/umap/locale/hr.json +51 -16
- umap/static/umap/locale/hu.js +51 -16
- umap/static/umap/locale/hu.json +51 -16
- umap/static/umap/locale/id.js +51 -16
- umap/static/umap/locale/id.json +51 -16
- umap/static/umap/locale/is.js +51 -16
- umap/static/umap/locale/is.json +51 -16
- umap/static/umap/locale/it.js +51 -16
- umap/static/umap/locale/it.json +51 -16
- umap/static/umap/locale/ja.js +51 -16
- umap/static/umap/locale/ja.json +51 -16
- umap/static/umap/locale/ko.js +51 -16
- umap/static/umap/locale/ko.json +51 -16
- umap/static/umap/locale/lt.js +51 -16
- umap/static/umap/locale/lt.json +51 -16
- umap/static/umap/locale/ms.js +51 -16
- umap/static/umap/locale/ms.json +51 -16
- umap/static/umap/locale/nl.js +51 -16
- umap/static/umap/locale/nl.json +51 -16
- umap/static/umap/locale/no.js +51 -16
- umap/static/umap/locale/no.json +51 -16
- umap/static/umap/locale/pl.js +93 -58
- umap/static/umap/locale/pl.json +93 -58
- umap/static/umap/locale/pl_PL.json +51 -16
- umap/static/umap/locale/pt.js +215 -180
- umap/static/umap/locale/pt.json +215 -180
- umap/static/umap/locale/pt_BR.js +51 -16
- umap/static/umap/locale/pt_BR.json +51 -16
- umap/static/umap/locale/pt_PT.js +51 -16
- umap/static/umap/locale/pt_PT.json +51 -16
- umap/static/umap/locale/ro.js +51 -16
- umap/static/umap/locale/ro.json +51 -16
- umap/static/umap/locale/ru.js +51 -16
- umap/static/umap/locale/ru.json +51 -16
- umap/static/umap/locale/si.js +51 -16
- umap/static/umap/locale/si.json +51 -16
- umap/static/umap/locale/sk_SK.js +51 -16
- umap/static/umap/locale/sk_SK.json +51 -16
- umap/static/umap/locale/sl.js +51 -16
- umap/static/umap/locale/sl.json +51 -16
- umap/static/umap/locale/sr.js +51 -16
- umap/static/umap/locale/sr.json +51 -16
- umap/static/umap/locale/sv.js +51 -16
- umap/static/umap/locale/sv.json +51 -16
- umap/static/umap/locale/th_TH.js +51 -16
- umap/static/umap/locale/th_TH.json +51 -16
- umap/static/umap/locale/tr.js +51 -16
- umap/static/umap/locale/tr.json +51 -16
- umap/static/umap/locale/uk_UA.js +51 -16
- umap/static/umap/locale/uk_UA.json +51 -16
- umap/static/umap/locale/vi.js +51 -16
- umap/static/umap/locale/vi.json +51 -16
- umap/static/umap/locale/vi_VN.json +51 -16
- umap/static/umap/locale/zh.js +51 -16
- umap/static/umap/locale/zh.json +51 -16
- umap/static/umap/locale/zh_CN.json +51 -16
- umap/static/umap/locale/zh_TW.Big5.json +51 -16
- umap/static/umap/locale/zh_TW.js +51 -16
- umap/static/umap/locale/zh_TW.json +51 -16
- umap/static/umap/map.css +40 -53
- umap/static/umap/unittests/sync.js +105 -0
- umap/static/umap/unittests/utils.js +78 -36
- umap/static/umap/vars.css +19 -1
- umap/static/umap/vendors/formbuilder/Leaflet.FormBuilder.js +2 -2
- umap/templates/umap/components/alerts/alert.html +89 -0
- umap/templates/umap/content.html +4 -3
- umap/templates/umap/css.html +4 -0
- umap/templates/umap/home.html +3 -0
- umap/templates/umap/js.html +0 -3
- umap/templates/umap/map_init.html +2 -8
- umap/templates/umap/messages.html +9 -11
- umap/templates/umap/search.html +3 -0
- umap/tests/base.py +2 -0
- umap/tests/integration/conftest.py +30 -0
- umap/tests/integration/test_anonymous_owned_map.py +8 -13
- umap/tests/integration/test_browser.py +77 -4
- umap/tests/integration/test_conditional_rules.py +201 -0
- umap/tests/integration/test_dashboard.py +1 -1
- umap/tests/integration/test_datalayer.py +2 -3
- umap/tests/integration/test_edit_datalayer.py +4 -4
- umap/tests/integration/test_edit_map.py +1 -1
- umap/tests/integration/test_facets_browser.py +3 -3
- umap/tests/integration/test_import.py +185 -49
- umap/tests/integration/test_map.py +31 -2
- umap/tests/integration/{test_collaborative_editing.py → test_optimistic_merge.py} +7 -7
- umap/tests/integration/test_owned_map.py +1 -1
- umap/tests/integration/test_picto.py +2 -2
- umap/tests/integration/test_statics.py +1 -1
- umap/tests/integration/test_view_marker.py +2 -2
- umap/tests/integration/test_websocket_sync.py +283 -0
- umap/tests/settings.py +5 -0
- umap/tests/test_datalayer_views.py +0 -1
- umap/tests/test_views.py +53 -0
- umap/urls.py +5 -0
- umap/views.py +40 -11
- umap/websocket_server.py +92 -0
- {umap_project-2.3.1.dist-info → umap_project-2.4.0.dist-info}/METADATA +10 -8
- {umap_project-2.3.1.dist-info → umap_project-2.4.0.dist-info}/RECORD +201 -167
- umap/static/umap/js/umap.autocomplete.js +0 -341
- umap/static/umap/js/umap.importer.js +0 -187
- umap/static/umap/js/umap.ui.js +0 -190
- {umap_project-2.3.1.dist-info → umap_project-2.4.0.dist-info}/WHEEL +0 -0
- {umap_project-2.3.1.dist-info → umap_project-2.4.0.dist-info}/entry_points.txt +0 -0
- {umap_project-2.3.1.dist-info → umap_project-2.4.0.dist-info}/licenses/LICENSE +0 -0
umap/static/umap/js/umap.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
L.Map.mergeOptions({
|
|
2
|
-
overlay:
|
|
2
|
+
overlay: {},
|
|
3
3
|
datalayers: [],
|
|
4
4
|
hash: true,
|
|
5
5
|
maxZoomLimit: 24,
|
|
@@ -20,9 +20,6 @@ L.Map.mergeOptions({
|
|
|
20
20
|
enablePolygonDraw: true,
|
|
21
21
|
enablePolylineDraw: true,
|
|
22
22
|
limitBounds: {},
|
|
23
|
-
importPresets: [
|
|
24
|
-
// {url: 'http://localhost:8019/en/datalayer/1502/', label: 'Simplified World Countries', format: 'geojson'}
|
|
25
|
-
],
|
|
26
23
|
slideshow: {},
|
|
27
24
|
clickable: true,
|
|
28
25
|
permissions: {},
|
|
@@ -33,6 +30,8 @@ U.Map = L.Map.extend({
|
|
|
33
30
|
includes: [ControlsMixin],
|
|
34
31
|
|
|
35
32
|
initialize: function (el, geojson) {
|
|
33
|
+
this.sync_engine = new U.SyncEngine(this)
|
|
34
|
+
this.sync = this.sync_engine.proxy(this)
|
|
36
35
|
// Locale name (pt_PT, en_US…)
|
|
37
36
|
// To be used for Django localization
|
|
38
37
|
if (geojson.properties.locale) L.setLocale(geojson.properties.locale)
|
|
@@ -57,15 +56,16 @@ U.Map = L.Map.extend({
|
|
|
57
56
|
this.urls = new U.URLs(this.options.urls)
|
|
58
57
|
|
|
59
58
|
this.panel = new U.Panel(this)
|
|
59
|
+
this.tooltip = new U.Tooltip(this._controlContainer)
|
|
60
|
+
this.dialog = new U.Dialog(this._controlContainer)
|
|
60
61
|
if (this.hasEditMode()) {
|
|
61
62
|
this.editPanel = new U.EditPanel(this)
|
|
62
63
|
this.fullPanel = new U.FullPanel(this)
|
|
63
64
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
this.
|
|
67
|
-
this.
|
|
68
|
-
this.request = new U.Request(this.ui)
|
|
65
|
+
L.DomEvent.on(document.body, 'dataloading', (e) => this.fire('dataloading', e))
|
|
66
|
+
L.DomEvent.on(document.body, 'dataload', (e) => this.fire('dataload', e))
|
|
67
|
+
this.server = new U.ServerRequest()
|
|
68
|
+
this.request = new U.Request()
|
|
69
69
|
|
|
70
70
|
this.initLoader()
|
|
71
71
|
this.name = this.options.name
|
|
@@ -205,42 +205,9 @@ U.Map = L.Map.extend({
|
|
|
205
205
|
this.editTools = new U.Editable(this)
|
|
206
206
|
this.renderEditToolbar()
|
|
207
207
|
}
|
|
208
|
+
|
|
208
209
|
this.initShortcuts()
|
|
209
|
-
this.onceDataLoaded(
|
|
210
|
-
const slug = L.Util.queryString('feature')
|
|
211
|
-
if (slug && this.features_index[slug]) this.features_index[slug].view()
|
|
212
|
-
if (this.options.noControl) return
|
|
213
|
-
this.initCaptionBar()
|
|
214
|
-
if (L.Util.queryString('share')) {
|
|
215
|
-
this.share.open()
|
|
216
|
-
} else if (this.options.onLoadPanel === 'databrowser') {
|
|
217
|
-
this.panel.setDefaultMode('expanded')
|
|
218
|
-
this.openBrowser('data')
|
|
219
|
-
} else if (this.options.onLoadPanel === 'datalayers') {
|
|
220
|
-
this.panel.setDefaultMode('condensed')
|
|
221
|
-
this.openBrowser('layers')
|
|
222
|
-
} else if (this.options.onLoadPanel === 'datafilters') {
|
|
223
|
-
this.panel.setDefaultMode('expanded')
|
|
224
|
-
this.openBrowser('filters')
|
|
225
|
-
} else if (this.options.onLoadPanel === 'caption') {
|
|
226
|
-
this.panel.setDefaultMode('condensed')
|
|
227
|
-
this.openCaption()
|
|
228
|
-
}
|
|
229
|
-
if (L.Util.queryString('edit')) {
|
|
230
|
-
if (this.hasEditMode()) this.enableEdit()
|
|
231
|
-
// Sometimes users share the ?edit link by mistake, let's remove
|
|
232
|
-
// this search parameter from URL to prevent this
|
|
233
|
-
const url = new URL(window.location)
|
|
234
|
-
url.searchParams.delete('edit')
|
|
235
|
-
history.pushState({}, '', url)
|
|
236
|
-
}
|
|
237
|
-
if (L.Util.queryString('download')) {
|
|
238
|
-
const download_url = this.urls.get('map_download', {
|
|
239
|
-
map_id: this.options.umap_id,
|
|
240
|
-
})
|
|
241
|
-
window.location = download_url
|
|
242
|
-
}
|
|
243
|
-
})
|
|
210
|
+
this.onceDataLoaded(this.setViewFromQueryString)
|
|
244
211
|
|
|
245
212
|
window.onbeforeunload = () => (this.editEnabled && this.isDirty) || null
|
|
246
213
|
this.backup()
|
|
@@ -248,6 +215,25 @@ U.Map = L.Map.extend({
|
|
|
248
215
|
this.on('click contextmenu.show', this.closeInplaceToolbar)
|
|
249
216
|
},
|
|
250
217
|
|
|
218
|
+
initSyncEngine: async function () {
|
|
219
|
+
if (this.options.websocketEnabled == false) return
|
|
220
|
+
if (this.options.syncEnabled != true) {
|
|
221
|
+
this.sync.stop()
|
|
222
|
+
} else {
|
|
223
|
+
const ws_token_uri = this.urls.get('map_websocket_auth_token', {
|
|
224
|
+
map_id: this.options.umap_id,
|
|
225
|
+
})
|
|
226
|
+
await this.sync.authenticate(ws_token_uri, this.options.websocketURI, this.server)
|
|
227
|
+
}
|
|
228
|
+
},
|
|
229
|
+
|
|
230
|
+
getSyncMetadata: function () {
|
|
231
|
+
return {
|
|
232
|
+
engine: this.sync,
|
|
233
|
+
subject: 'map',
|
|
234
|
+
}
|
|
235
|
+
},
|
|
236
|
+
|
|
251
237
|
render: function (fields) {
|
|
252
238
|
let impacts = U.Utils.getImpactsFromSchema(fields)
|
|
253
239
|
|
|
@@ -271,6 +257,8 @@ U.Map = L.Map.extend({
|
|
|
271
257
|
case 'bounds':
|
|
272
258
|
this.handleLimitBounds()
|
|
273
259
|
break
|
|
260
|
+
case 'sync':
|
|
261
|
+
this.initSyncEngine()
|
|
274
262
|
}
|
|
275
263
|
}
|
|
276
264
|
},
|
|
@@ -313,6 +301,44 @@ U.Map = L.Map.extend({
|
|
|
313
301
|
}
|
|
314
302
|
},
|
|
315
303
|
|
|
304
|
+
setViewFromQueryString: function () {
|
|
305
|
+
if (this.options.noControl) return
|
|
306
|
+
this.initCaptionBar()
|
|
307
|
+
if (L.Util.queryString('share')) {
|
|
308
|
+
this.share.open()
|
|
309
|
+
} else if (this.options.onLoadPanel === 'databrowser') {
|
|
310
|
+
this.panel.setDefaultMode('expanded')
|
|
311
|
+
this.openBrowser('data')
|
|
312
|
+
} else if (this.options.onLoadPanel === 'datalayers') {
|
|
313
|
+
this.panel.setDefaultMode('condensed')
|
|
314
|
+
this.openBrowser('layers')
|
|
315
|
+
} else if (this.options.onLoadPanel === 'datafilters') {
|
|
316
|
+
this.panel.setDefaultMode('expanded')
|
|
317
|
+
this.openBrowser('filters')
|
|
318
|
+
} else if (this.options.onLoadPanel === 'caption') {
|
|
319
|
+
this.panel.setDefaultMode('condensed')
|
|
320
|
+
this.openCaption()
|
|
321
|
+
}
|
|
322
|
+
// Comes after default panels, so if it opens in a panel it will
|
|
323
|
+
// take precedence.
|
|
324
|
+
const slug = L.Util.queryString('feature')
|
|
325
|
+
if (slug && this.features_index[slug]) this.features_index[slug].view()
|
|
326
|
+
if (L.Util.queryString('edit')) {
|
|
327
|
+
if (this.hasEditMode()) this.enableEdit()
|
|
328
|
+
// Sometimes users share the ?edit link by mistake, let's remove
|
|
329
|
+
// this search parameter from URL to prevent this
|
|
330
|
+
const url = new URL(window.location)
|
|
331
|
+
url.searchParams.delete('edit')
|
|
332
|
+
history.pushState({}, '', url)
|
|
333
|
+
}
|
|
334
|
+
if (L.Util.queryString('download')) {
|
|
335
|
+
const download_url = this.urls.get('map_download', {
|
|
336
|
+
map_id: this.options.umap_id,
|
|
337
|
+
})
|
|
338
|
+
window.location = download_url
|
|
339
|
+
}
|
|
340
|
+
},
|
|
341
|
+
|
|
316
342
|
// Merge the given schema with the default one
|
|
317
343
|
// Missing keys inside the schema are merged with the default ones.
|
|
318
344
|
overrideSchema: function (schema) {
|
|
@@ -359,7 +385,7 @@ U.Map = L.Map.extend({
|
|
|
359
385
|
icon: 'umap-fake-class',
|
|
360
386
|
iconLoading: 'umap-fake-class',
|
|
361
387
|
flyTo: this.options.easing,
|
|
362
|
-
onLocationError: (err) =>
|
|
388
|
+
onLocationError: (err) => U.Alert.error(err.message),
|
|
363
389
|
})
|
|
364
390
|
this._controls.fullscreen = new L.Control.Fullscreen({
|
|
365
391
|
title: { false: L._('View Fullscreen'), true: L._('Exit Fullscreen') },
|
|
@@ -388,11 +414,14 @@ U.Map = L.Map.extend({
|
|
|
388
414
|
this.importer = new U.Importer(this)
|
|
389
415
|
this.drop = new U.DropControl(this)
|
|
390
416
|
this.share = new U.Share(this)
|
|
417
|
+
this.rules = new U.Rules(this)
|
|
391
418
|
this._controls.tilelayers = new U.TileLayerControl(this)
|
|
392
419
|
},
|
|
393
420
|
|
|
394
421
|
renderControls: function () {
|
|
395
|
-
const hasSlideshow = Boolean(
|
|
422
|
+
const hasSlideshow = Boolean(
|
|
423
|
+
this.options.slideshow && this.options.slideshow.active
|
|
424
|
+
)
|
|
396
425
|
const barEnabled = this.options.captionBar || hasSlideshow
|
|
397
426
|
document.body.classList.toggle('umap-caption-bar-enabled', barEnabled)
|
|
398
427
|
document.body.classList.toggle('umap-slideshow-enabled', hasSlideshow)
|
|
@@ -514,73 +543,80 @@ U.Map = L.Map.extend({
|
|
|
514
543
|
|
|
515
544
|
initShortcuts: function () {
|
|
516
545
|
const globalShortcuts = function (e) {
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
} else {
|
|
528
|
-
this.panel.close()
|
|
546
|
+
if (e.key === 'Escape') {
|
|
547
|
+
if (this.dialog.visible) {
|
|
548
|
+
this.dialog.close()
|
|
549
|
+
} else if (this.importer.dialog.visible) {
|
|
550
|
+
this.importer.dialog.close()
|
|
551
|
+
} else if (this.editEnabled && this.editTools.drawing()) {
|
|
552
|
+
this.editTools.stopDrawing()
|
|
553
|
+
} else if (this.measureTools.enabled()) {
|
|
554
|
+
this.measureTools.stopDrawing()
|
|
555
|
+
} else if (this.editPanel?.isOpen()) {
|
|
529
556
|
this.editPanel?.close()
|
|
557
|
+
} else if (this.fullPanel?.isOpen()) {
|
|
530
558
|
this.fullPanel?.close()
|
|
559
|
+
} else if (this.panel.isOpen()) {
|
|
560
|
+
this.panel.close()
|
|
531
561
|
}
|
|
532
562
|
}
|
|
533
563
|
|
|
534
|
-
|
|
564
|
+
// From now on, only ctrl/meta shortcut
|
|
565
|
+
if (!(e.ctrlKey || e.metaKey) || e.shiftKey) return
|
|
535
566
|
|
|
536
|
-
|
|
537
|
-
if (key === U.Keys.E && hasModifier && !this.editEnabled) {
|
|
567
|
+
if (e.key === 'f') {
|
|
538
568
|
L.DomEvent.stop(e)
|
|
539
|
-
this.
|
|
540
|
-
} else if (key === U.Keys.E && hasModifier && this.editEnabled && !this.isDirty) {
|
|
541
|
-
L.DomEvent.stop(e)
|
|
542
|
-
this.disableEdit()
|
|
569
|
+
this.search()
|
|
543
570
|
}
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
571
|
+
|
|
572
|
+
/* Edit mode only shortcuts */
|
|
573
|
+
if (!this.hasEditMode()) return
|
|
574
|
+
|
|
575
|
+
// Edit mode Off
|
|
576
|
+
if (!this.editEnabled) {
|
|
577
|
+
switch (e.key) {
|
|
578
|
+
case 'e':
|
|
579
|
+
L.DomEvent.stop(e)
|
|
580
|
+
this.enableEdit()
|
|
581
|
+
break
|
|
548
582
|
}
|
|
583
|
+
return
|
|
549
584
|
}
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
585
|
+
|
|
586
|
+
// Edit mode on
|
|
587
|
+
let used = true
|
|
588
|
+
switch (e.key) {
|
|
589
|
+
case 'e':
|
|
590
|
+
if (!this.isDirty) this.disableEdit()
|
|
591
|
+
break
|
|
592
|
+
case 's':
|
|
593
|
+
if (this.isDirty) this.save()
|
|
594
|
+
break
|
|
595
|
+
case 'z':
|
|
596
|
+
if (this.isDirty) this.askForReset()
|
|
597
|
+
break
|
|
598
|
+
case 'm':
|
|
599
|
+
this.editTools.startMarker()
|
|
600
|
+
break
|
|
601
|
+
case 'p':
|
|
602
|
+
this.editTools.startPolygon()
|
|
603
|
+
break
|
|
604
|
+
case 'l':
|
|
605
|
+
this.editTools.startPolyline()
|
|
606
|
+
break
|
|
607
|
+
case 'i':
|
|
608
|
+
this.importer.open()
|
|
609
|
+
break
|
|
610
|
+
case 'o':
|
|
611
|
+
this.importer.openFiles()
|
|
612
|
+
break
|
|
613
|
+
case 'h':
|
|
614
|
+
this.help.show('edit')
|
|
615
|
+
break
|
|
616
|
+
default:
|
|
617
|
+
used = false
|
|
583
618
|
}
|
|
619
|
+
if (used) L.DomEvent.stop(e)
|
|
584
620
|
}
|
|
585
621
|
L.DomEvent.addListener(document, 'keydown', globalShortcuts, this)
|
|
586
622
|
},
|
|
@@ -641,10 +677,7 @@ U.Map = L.Map.extend({
|
|
|
641
677
|
} catch (e) {
|
|
642
678
|
console.error(e)
|
|
643
679
|
this.removeLayer(tilelayer)
|
|
644
|
-
|
|
645
|
-
content: `${L._('Error in the tilelayer URL')}: ${tilelayer._url}`,
|
|
646
|
-
level: 'error',
|
|
647
|
-
})
|
|
680
|
+
U.Alert.error(`${L._('Error in the tilelayer URL')}: ${tilelayer._url}`)
|
|
648
681
|
// Users can put tilelayer URLs by hand, and if they add wrong {variable},
|
|
649
682
|
// Leaflet throw an error, and then the map is no more editable
|
|
650
683
|
}
|
|
@@ -676,10 +709,7 @@ U.Map = L.Map.extend({
|
|
|
676
709
|
} catch (e) {
|
|
677
710
|
this.removeLayer(overlay)
|
|
678
711
|
console.error(e)
|
|
679
|
-
|
|
680
|
-
content: `${L._('Error in the overlay URL')}: ${overlay._url}`,
|
|
681
|
-
level: 'error',
|
|
682
|
-
})
|
|
712
|
+
U.Alert.error(`${L._('Error in the overlay URL')}: ${overlay._url}`)
|
|
683
713
|
}
|
|
684
714
|
},
|
|
685
715
|
|
|
@@ -770,11 +800,14 @@ U.Map = L.Map.extend({
|
|
|
770
800
|
return L.Map.prototype.setMaxBounds.call(this, bounds)
|
|
771
801
|
},
|
|
772
802
|
|
|
773
|
-
createDataLayer: function (
|
|
774
|
-
|
|
775
|
-
|
|
803
|
+
createDataLayer: function (options = {}, sync) {
|
|
804
|
+
options.name = options.name || `${L._('Layer')} ${this.datalayers_index.length + 1}`
|
|
805
|
+
const datalayer = new U.DataLayer(this, options, sync)
|
|
806
|
+
|
|
807
|
+
if (sync !== false) {
|
|
808
|
+
datalayer.sync.upsert(datalayer.options)
|
|
776
809
|
}
|
|
777
|
-
return
|
|
810
|
+
return datalayer
|
|
778
811
|
},
|
|
779
812
|
|
|
780
813
|
newDataLayer: function () {
|
|
@@ -786,24 +819,25 @@ U.Map = L.Map.extend({
|
|
|
786
819
|
return U.SCHEMA[option] && U.SCHEMA[option].default
|
|
787
820
|
},
|
|
788
821
|
|
|
789
|
-
getOption: function (option) {
|
|
822
|
+
getOption: function (option, feature) {
|
|
823
|
+
if (feature) {
|
|
824
|
+
const value = this.rules.getOption(option, feature)
|
|
825
|
+
if (value !== undefined) return value
|
|
826
|
+
}
|
|
790
827
|
if (U.Utils.usableOption(this.options, option)) return this.options[option]
|
|
791
828
|
return this.getDefaultOption(option)
|
|
792
829
|
},
|
|
793
830
|
|
|
794
|
-
|
|
831
|
+
setCenterAndZoom: function () {
|
|
832
|
+
this._setCenterAndZoom()
|
|
833
|
+
U.Alert.success(L._('The zoom and center have been modified.'))
|
|
834
|
+
},
|
|
835
|
+
|
|
836
|
+
_setCenterAndZoom: function () {
|
|
795
837
|
this.options.center = this.getCenter()
|
|
796
838
|
this.options.zoom = this.getZoom()
|
|
797
839
|
this.isDirty = true
|
|
798
840
|
this._default_extent = false
|
|
799
|
-
if (this.options.umap_id) {
|
|
800
|
-
// We do not want an extra message during the map creation
|
|
801
|
-
// to avoid the double notification/alert.
|
|
802
|
-
this.ui.alert({
|
|
803
|
-
content: L._('The zoom and center have been modified.'),
|
|
804
|
-
level: 'info',
|
|
805
|
-
})
|
|
806
|
-
}
|
|
807
841
|
},
|
|
808
842
|
|
|
809
843
|
updateTileLayers: function () {
|
|
@@ -842,12 +876,11 @@ U.Map = L.Map.extend({
|
|
|
842
876
|
processFileToImport: function (file, layer, type) {
|
|
843
877
|
type = type || U.Utils.detectFileType(file)
|
|
844
878
|
if (!type) {
|
|
845
|
-
|
|
846
|
-
|
|
879
|
+
U.Alert.error(
|
|
880
|
+
L._('Unable to detect format of file {filename}', {
|
|
847
881
|
filename: file.name,
|
|
848
|
-
})
|
|
849
|
-
|
|
850
|
-
})
|
|
882
|
+
})
|
|
883
|
+
)
|
|
851
884
|
return
|
|
852
885
|
}
|
|
853
886
|
if (type === 'umap') {
|
|
@@ -858,6 +891,13 @@ U.Map = L.Map.extend({
|
|
|
858
891
|
}
|
|
859
892
|
},
|
|
860
893
|
|
|
894
|
+
importFromUrl: async function (uri) {
|
|
895
|
+
const response = await this.request.get(uri)
|
|
896
|
+
if (response && response.ok) {
|
|
897
|
+
this.importRaw(await response.text())
|
|
898
|
+
}
|
|
899
|
+
},
|
|
900
|
+
|
|
861
901
|
importRaw: function (rawData) {
|
|
862
902
|
const importedData = JSON.parse(rawData)
|
|
863
903
|
|
|
@@ -873,7 +913,11 @@ U.Map = L.Map.extend({
|
|
|
873
913
|
if (importedData.geometry) this.options.center = this.latLng(importedData.geometry)
|
|
874
914
|
const self = this
|
|
875
915
|
importedData.layers.forEach((geojson) => {
|
|
876
|
-
|
|
916
|
+
if (!geojson._umap_options && geojson._storage) {
|
|
917
|
+
geojson._umap_options = geojson._storage
|
|
918
|
+
delete geojson._storage
|
|
919
|
+
}
|
|
920
|
+
delete geojson._umap_options?.id // Never trust an id at this stage
|
|
877
921
|
const dataLayer = self.createDataLayer(geojson._umap_options)
|
|
878
922
|
dataLayer.fromUmapGeoJSON(geojson)
|
|
879
923
|
})
|
|
@@ -899,10 +943,7 @@ U.Map = L.Map.extend({
|
|
|
899
943
|
self.importRaw(rawData)
|
|
900
944
|
} catch (e) {
|
|
901
945
|
console.error('Error importing data', e)
|
|
902
|
-
|
|
903
|
-
content: L._('Invalid umap data in {filename}', { filename: file.name }),
|
|
904
|
-
level: 'error',
|
|
905
|
-
})
|
|
946
|
+
U.Alert.error(L._('Invalid umap data in {filename}', { filename: file.name }))
|
|
906
947
|
}
|
|
907
948
|
}
|
|
908
949
|
},
|
|
@@ -997,6 +1038,7 @@ U.Map = L.Map.extend({
|
|
|
997
1038
|
},
|
|
998
1039
|
|
|
999
1040
|
saveSelf: async function () {
|
|
1041
|
+
this.rules.commit()
|
|
1000
1042
|
const geojson = {
|
|
1001
1043
|
type: 'Feature',
|
|
1002
1044
|
geometry: this.geometry(),
|
|
@@ -1007,65 +1049,56 @@ U.Map = L.Map.extend({
|
|
|
1007
1049
|
formData.append('center', JSON.stringify(this.geometry()))
|
|
1008
1050
|
formData.append('settings', JSON.stringify(geojson))
|
|
1009
1051
|
const uri = this.urls.get('map_save', { map_id: this.options.umap_id })
|
|
1010
|
-
const [data,
|
|
1052
|
+
const [data, _, error] = await this.server.post(uri, {}, formData)
|
|
1011
1053
|
// FIXME: login_required response will not be an error, so it will not
|
|
1012
1054
|
// stop code while it should
|
|
1013
|
-
if (
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
L._(
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
},
|
|
1040
|
-
]
|
|
1041
|
-
if (this.options.urls.map_send_edit_link) {
|
|
1042
|
-
alert.actions.push({
|
|
1043
|
-
label: L._('Send me the link'),
|
|
1044
|
-
input: L._('Email'),
|
|
1045
|
-
callback: this.sendEditLink,
|
|
1046
|
-
callbackContext: this,
|
|
1047
|
-
})
|
|
1048
|
-
}
|
|
1049
|
-
}
|
|
1050
|
-
} else if (!this.permissions.isDirty) {
|
|
1055
|
+
if (error) {
|
|
1056
|
+
return
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
if (!this.options.umap_id) {
|
|
1060
|
+
this.options.umap_id = data.id
|
|
1061
|
+
this.permissions.setOptions(data.permissions)
|
|
1062
|
+
this.permissions.commit()
|
|
1063
|
+
if (data?.permissions?.anonymous_edit_url) {
|
|
1064
|
+
this.once('saved', () => {
|
|
1065
|
+
U.AlertCreation.info(
|
|
1066
|
+
L._('Your map has been created with an anonymous account!'),
|
|
1067
|
+
Number.Infinity,
|
|
1068
|
+
data.permissions.anonymous_edit_url,
|
|
1069
|
+
this.options.urls.map_send_edit_link
|
|
1070
|
+
? this.sendEditLinkEmail.bind(this)
|
|
1071
|
+
: null
|
|
1072
|
+
)
|
|
1073
|
+
})
|
|
1074
|
+
} else {
|
|
1075
|
+
this.once('saved', () => {
|
|
1076
|
+
U.Alert.success(L._('Congratulations, your map has been created!'))
|
|
1077
|
+
})
|
|
1078
|
+
}
|
|
1079
|
+
} else {
|
|
1080
|
+
if (!this.permissions.isDirty) {
|
|
1051
1081
|
// Do not override local changes to permissions,
|
|
1052
1082
|
// but update in case some other editors changed them in the meantime.
|
|
1053
1083
|
this.permissions.setOptions(data.permissions)
|
|
1054
1084
|
this.permissions.commit()
|
|
1055
1085
|
}
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
this.
|
|
1086
|
+
this.once('saved', () => {
|
|
1087
|
+
U.Alert.success(data.info || L._('Map has been saved!'))
|
|
1088
|
+
})
|
|
1089
|
+
}
|
|
1090
|
+
// Update URL in case the name has changed.
|
|
1091
|
+
if (history?.pushState) {
|
|
1092
|
+
history.pushState({}, this.options.name, data.url)
|
|
1093
|
+
} else {
|
|
1094
|
+
window.location = data.url
|
|
1063
1095
|
}
|
|
1096
|
+
this.permissions.save()
|
|
1064
1097
|
},
|
|
1065
1098
|
|
|
1066
1099
|
save: function () {
|
|
1067
1100
|
if (!this.isDirty) return
|
|
1068
|
-
if (this._default_extent) this.
|
|
1101
|
+
if (this._default_extent) this._setCenterAndZoom()
|
|
1069
1102
|
this.backup()
|
|
1070
1103
|
this.once('saved', () => {
|
|
1071
1104
|
this.isDirty = false
|
|
@@ -1078,33 +1111,20 @@ U.Map = L.Map.extend({
|
|
|
1078
1111
|
}
|
|
1079
1112
|
},
|
|
1080
1113
|
|
|
1081
|
-
sendEditLink: async function () {
|
|
1082
|
-
const input = this.ui._alert.querySelector('input')
|
|
1083
|
-
const email = input.value
|
|
1084
|
-
|
|
1085
|
-
const formData = new FormData()
|
|
1086
|
-
formData.append('email', email)
|
|
1087
|
-
|
|
1088
|
-
const url = this.urls.get('map_send_edit_link', { map_id: this.options.umap_id })
|
|
1089
|
-
await this.server.post(url, {}, formData)
|
|
1090
|
-
},
|
|
1091
|
-
|
|
1092
1114
|
star: async function () {
|
|
1093
|
-
if (!this.options.umap_id)
|
|
1094
|
-
return
|
|
1095
|
-
|
|
1096
|
-
level: 'error',
|
|
1097
|
-
})
|
|
1115
|
+
if (!this.options.umap_id) {
|
|
1116
|
+
return U.Alert.error(L._('Please save the map first'))
|
|
1117
|
+
}
|
|
1098
1118
|
const url = this.urls.get('map_star', { map_id: this.options.umap_id })
|
|
1099
1119
|
const [data, response, error] = await this.server.post(url)
|
|
1100
|
-
if (
|
|
1101
|
-
|
|
1102
|
-
let msg = data.starred
|
|
1103
|
-
? L._('Map has been starred')
|
|
1104
|
-
: L._('Map has been unstarred')
|
|
1105
|
-
this.ui.alert({ content: msg, level: 'info' })
|
|
1106
|
-
this.renderControls()
|
|
1120
|
+
if (error) {
|
|
1121
|
+
return
|
|
1107
1122
|
}
|
|
1123
|
+
this.options.starred = data.starred
|
|
1124
|
+
U.Alert.success(
|
|
1125
|
+
data.starred ? L._('Map has been starred') : L._('Map has been unstarred')
|
|
1126
|
+
)
|
|
1127
|
+
this.renderControls()
|
|
1108
1128
|
},
|
|
1109
1129
|
|
|
1110
1130
|
geometry: function () {
|
|
@@ -1408,6 +1428,8 @@ U.Map = L.Map.extend({
|
|
|
1408
1428
|
this.options.limitBounds.north = L.Util.formatNum(bounds.getNorth())
|
|
1409
1429
|
this.options.limitBounds.east = L.Util.formatNum(bounds.getEast())
|
|
1410
1430
|
boundsBuilder.fetchAll()
|
|
1431
|
+
|
|
1432
|
+
this.sync.update(this, 'options.limitBounds', this.options.limitBounds)
|
|
1411
1433
|
this.isDirty = true
|
|
1412
1434
|
this.handleLimitBounds()
|
|
1413
1435
|
},
|
|
@@ -1463,6 +1485,12 @@ U.Map = L.Map.extend({
|
|
|
1463
1485
|
slideshow.appendChild(slideshowBuilder.build())
|
|
1464
1486
|
},
|
|
1465
1487
|
|
|
1488
|
+
_editSync: function (container) {
|
|
1489
|
+
const sync = L.DomUtil.createFieldset(container, L._('Real-time collaboration'))
|
|
1490
|
+
const builder = new U.FormBuilder(this, ['options.syncEnabled'])
|
|
1491
|
+
sync.appendChild(builder.build())
|
|
1492
|
+
},
|
|
1493
|
+
|
|
1466
1494
|
_advancedActions: function (container) {
|
|
1467
1495
|
const advancedActions = L.DomUtil.createFieldset(container, L._('Advanced actions'))
|
|
1468
1496
|
const advancedButtons = L.DomUtil.create('div', 'button-bar half', advancedActions)
|
|
@@ -1508,10 +1536,10 @@ U.Map = L.Map.extend({
|
|
|
1508
1536
|
editCaption: function () {
|
|
1509
1537
|
if (!this.editEnabled) return
|
|
1510
1538
|
if (this.options.editMode !== 'advanced') return
|
|
1511
|
-
const container = L.DomUtil.create('div', 'umap-edit-container')
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1539
|
+
const container = L.DomUtil.create('div', 'umap-edit-container')
|
|
1540
|
+
const metadataFields = ['options.name', 'options.description']
|
|
1541
|
+
|
|
1542
|
+
L.DomUtil.createTitle(container, L._('Edit map details'), 'icon-caption')
|
|
1515
1543
|
const builder = new U.FormBuilder(this, metadataFields, {
|
|
1516
1544
|
className: 'map-metadata',
|
|
1517
1545
|
})
|
|
@@ -1540,10 +1568,14 @@ U.Map = L.Map.extend({
|
|
|
1540
1568
|
this._editShapeProperties(container)
|
|
1541
1569
|
this._editDefaultProperties(container)
|
|
1542
1570
|
this._editInteractionsProperties(container)
|
|
1571
|
+
this.rules.edit(container)
|
|
1543
1572
|
this._editTilelayer(container)
|
|
1544
1573
|
this._editOverlay(container)
|
|
1545
1574
|
this._editBounds(container)
|
|
1546
1575
|
this._editSlideshow(container)
|
|
1576
|
+
if (this.options.websocketEnabled) {
|
|
1577
|
+
this._editSync(container)
|
|
1578
|
+
}
|
|
1547
1579
|
this._advancedActions(container)
|
|
1548
1580
|
|
|
1549
1581
|
this.editPanel.open({ content: container, className: 'dark' })
|
|
@@ -1554,6 +1586,7 @@ U.Map = L.Map.extend({
|
|
|
1554
1586
|
this.editEnabled = true
|
|
1555
1587
|
this.drop.enable()
|
|
1556
1588
|
this.fire('edit:enabled')
|
|
1589
|
+
this.initSyncEngine()
|
|
1557
1590
|
},
|
|
1558
1591
|
|
|
1559
1592
|
disableEdit: function () {
|
|
@@ -1565,6 +1598,7 @@ U.Map = L.Map.extend({
|
|
|
1565
1598
|
this.fire('edit:disabled')
|
|
1566
1599
|
this.editPanel.close()
|
|
1567
1600
|
this.fullPanel.close()
|
|
1601
|
+
this.sync.stop()
|
|
1568
1602
|
},
|
|
1569
1603
|
|
|
1570
1604
|
hasEditMode: function () {
|
|
@@ -1588,7 +1622,7 @@ U.Map = L.Map.extend({
|
|
|
1588
1622
|
L.DomUtil.createButton(
|
|
1589
1623
|
'umap-about-link flat',
|
|
1590
1624
|
container,
|
|
1591
|
-
L._('
|
|
1625
|
+
L._('Open caption'),
|
|
1592
1626
|
this.openCaption,
|
|
1593
1627
|
this
|
|
1594
1628
|
)
|
|
@@ -1739,7 +1773,7 @@ U.Map = L.Map.extend({
|
|
|
1739
1773
|
items.push(
|
|
1740
1774
|
'-',
|
|
1741
1775
|
{
|
|
1742
|
-
text: L._('
|
|
1776
|
+
text: L._('Open browser'),
|
|
1743
1777
|
callback: () => this.openBrowser('layers'),
|
|
1744
1778
|
},
|
|
1745
1779
|
{
|
|
@@ -1755,7 +1789,7 @@ U.Map = L.Map.extend({
|
|
|
1755
1789
|
}
|
|
1756
1790
|
items.push(
|
|
1757
1791
|
{
|
|
1758
|
-
text: L._('
|
|
1792
|
+
text: L._('Open caption'),
|
|
1759
1793
|
callback: this.openCaption,
|
|
1760
1794
|
},
|
|
1761
1795
|
{
|
|
@@ -1842,10 +1876,6 @@ U.Map = L.Map.extend({
|
|
|
1842
1876
|
if (this._controls.search) this._controls.search.open()
|
|
1843
1877
|
},
|
|
1844
1878
|
|
|
1845
|
-
getFilterKeys: function () {
|
|
1846
|
-
return (this.options.filterKey || this.options.sortKey || 'name').split(',')
|
|
1847
|
-
},
|
|
1848
|
-
|
|
1849
1879
|
getLayersBounds: function () {
|
|
1850
1880
|
const bounds = new L.latLngBounds()
|
|
1851
1881
|
this.eachBrowsableDataLayer((d) => {
|
|
@@ -1853,4 +1883,13 @@ U.Map = L.Map.extend({
|
|
|
1853
1883
|
})
|
|
1854
1884
|
return bounds
|
|
1855
1885
|
},
|
|
1886
|
+
|
|
1887
|
+
sendEditLinkEmail: async function (formData) {
|
|
1888
|
+
const sendLink =
|
|
1889
|
+
this.options.urls.map_send_edit_link &&
|
|
1890
|
+
this.urls.get('map_send_edit_link', {
|
|
1891
|
+
map_id: this.options.umap_id,
|
|
1892
|
+
})
|
|
1893
|
+
await this.server.post(sendLink, {}, formData)
|
|
1894
|
+
},
|
|
1856
1895
|
})
|