umap-project 2.8.0a2__py3-none-any.whl → 2.8.1__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/decorators.py +3 -1
- umap/locale/ar/LC_MESSAGES/django.mo +0 -0
- umap/locale/ar/LC_MESSAGES/django.po +45 -30
- umap/locale/br/LC_MESSAGES/django.mo +0 -0
- umap/locale/br/LC_MESSAGES/django.po +49 -34
- umap/locale/ca/LC_MESSAGES/django.mo +0 -0
- umap/locale/ca/LC_MESSAGES/django.po +45 -30
- umap/locale/cs_CZ/LC_MESSAGES/django.mo +0 -0
- umap/locale/cs_CZ/LC_MESSAGES/django.po +52 -37
- umap/locale/da/LC_MESSAGES/django.mo +0 -0
- umap/locale/da/LC_MESSAGES/django.po +45 -30
- umap/locale/de/LC_MESSAGES/django.mo +0 -0
- umap/locale/de/LC_MESSAGES/django.po +45 -30
- umap/locale/el/LC_MESSAGES/django.mo +0 -0
- umap/locale/el/LC_MESSAGES/django.po +45 -30
- umap/locale/en/LC_MESSAGES/django.po +44 -29
- umap/locale/es/LC_MESSAGES/django.mo +0 -0
- umap/locale/es/LC_MESSAGES/django.po +45 -30
- umap/locale/et/LC_MESSAGES/django.mo +0 -0
- umap/locale/et/LC_MESSAGES/django.po +45 -30
- umap/locale/eu/LC_MESSAGES/django.mo +0 -0
- umap/locale/eu/LC_MESSAGES/django.po +65 -50
- umap/locale/fa_IR/LC_MESSAGES/django.mo +0 -0
- umap/locale/fa_IR/LC_MESSAGES/django.po +45 -30
- umap/locale/fr/LC_MESSAGES/django.mo +0 -0
- umap/locale/fr/LC_MESSAGES/django.po +45 -30
- umap/locale/gl/LC_MESSAGES/django.mo +0 -0
- umap/locale/gl/LC_MESSAGES/django.po +45 -30
- umap/locale/he/LC_MESSAGES/django.mo +0 -0
- umap/locale/he/LC_MESSAGES/django.po +45 -30
- umap/locale/hu/LC_MESSAGES/django.mo +0 -0
- umap/locale/hu/LC_MESSAGES/django.po +45 -30
- umap/locale/is/LC_MESSAGES/django.mo +0 -0
- umap/locale/is/LC_MESSAGES/django.po +45 -30
- umap/locale/it/LC_MESSAGES/django.mo +0 -0
- umap/locale/it/LC_MESSAGES/django.po +45 -30
- umap/locale/ja/LC_MESSAGES/django.mo +0 -0
- umap/locale/ja/LC_MESSAGES/django.po +45 -30
- umap/locale/ko/LC_MESSAGES/django.mo +0 -0
- umap/locale/ko/LC_MESSAGES/django.po +45 -30
- umap/locale/lt/LC_MESSAGES/django.mo +0 -0
- umap/locale/lt/LC_MESSAGES/django.po +45 -30
- umap/locale/ms/LC_MESSAGES/django.mo +0 -0
- umap/locale/ms/LC_MESSAGES/django.po +45 -30
- umap/locale/nl/LC_MESSAGES/django.mo +0 -0
- umap/locale/nl/LC_MESSAGES/django.po +45 -30
- umap/locale/pl/LC_MESSAGES/django.mo +0 -0
- umap/locale/pl/LC_MESSAGES/django.po +45 -30
- umap/locale/pt/LC_MESSAGES/django.mo +0 -0
- umap/locale/pt/LC_MESSAGES/django.po +45 -30
- umap/locale/pt_BR/LC_MESSAGES/django.mo +0 -0
- umap/locale/pt_BR/LC_MESSAGES/django.po +45 -30
- umap/locale/pt_PT/LC_MESSAGES/django.mo +0 -0
- umap/locale/pt_PT/LC_MESSAGES/django.po +45 -30
- umap/locale/ru/LC_MESSAGES/django.mo +0 -0
- umap/locale/ru/LC_MESSAGES/django.po +45 -30
- umap/locale/sk_SK/LC_MESSAGES/django.mo +0 -0
- umap/locale/sk_SK/LC_MESSAGES/django.po +45 -30
- umap/locale/sl/LC_MESSAGES/django.mo +0 -0
- umap/locale/sl/LC_MESSAGES/django.po +45 -30
- umap/locale/sr/LC_MESSAGES/django.mo +0 -0
- umap/locale/sr/LC_MESSAGES/django.po +45 -30
- umap/locale/sv/LC_MESSAGES/django.mo +0 -0
- umap/locale/sv/LC_MESSAGES/django.po +45 -30
- umap/locale/th_TH/LC_MESSAGES/django.mo +0 -0
- umap/locale/th_TH/LC_MESSAGES/django.po +45 -30
- umap/locale/tr/LC_MESSAGES/django.mo +0 -0
- umap/locale/tr/LC_MESSAGES/django.po +45 -30
- umap/locale/uk_UA/LC_MESSAGES/django.mo +0 -0
- umap/locale/uk_UA/LC_MESSAGES/django.po +45 -30
- umap/locale/zh_TW/LC_MESSAGES/django.mo +0 -0
- umap/locale/zh_TW/LC_MESSAGES/django.po +50 -35
- umap/settings/local_s3.py +45 -0
- umap/static/umap/content.css +18 -13
- umap/static/umap/css/bar.css +4 -0
- umap/static/umap/css/form.css +3 -0
- umap/static/umap/img/logo_lightcyan.svg +4 -0
- umap/static/umap/js/modules/caption.js +73 -73
- umap/static/umap/js/modules/data/features.js +20 -5
- umap/static/umap/js/modules/data/layer.js +17 -14
- umap/static/umap/js/modules/drop.js +55 -0
- umap/static/umap/js/modules/importer.js +20 -10
- umap/static/umap/js/modules/rendering/icon.js +2 -1
- umap/static/umap/js/modules/rendering/map.js +9 -8
- umap/static/umap/js/modules/rendering/popup.js +9 -10
- umap/static/umap/js/modules/rendering/template.js +53 -9
- umap/static/umap/js/modules/rendering/ui.js +6 -2
- umap/static/umap/js/modules/request.js +2 -2
- umap/static/umap/js/modules/schema.js +1 -0
- umap/static/umap/js/modules/sync/engine.js +56 -13
- umap/static/umap/js/modules/sync/updaters.js +4 -1
- umap/static/umap/js/modules/sync/websocket.js +47 -2
- umap/static/umap/js/modules/ui/bar.js +1 -1
- umap/static/umap/js/modules/ui/dialog.js +5 -0
- umap/static/umap/js/modules/umap.js +62 -25
- umap/static/umap/js/modules/utils.js +2 -0
- umap/static/umap/js/umap.controls.js +8 -55
- umap/static/umap/js/umap.forms.js +44 -0
- umap/static/umap/locale/cs_CZ.js +13 -11
- umap/static/umap/locale/cs_CZ.json +13 -11
- umap/static/umap/locale/en.js +2 -1
- umap/static/umap/locale/en.json +2 -1
- umap/static/umap/locale/fr.js +2 -1
- umap/static/umap/locale/fr.json +2 -1
- umap/static/umap/locale/zh_TW.js +13 -11
- umap/static/umap/locale/zh_TW.json +13 -11
- umap/static/umap/map.css +34 -166
- umap/static/umap/unittests/sync.js +4 -1
- umap/static/umap/vars.css +0 -1
- umap/templates/403.html +12 -0
- umap/templates/404.html +4 -13
- umap/templates/40x.html +9 -0
- umap/templates/base.html +2 -0
- umap/templates/umap/components/alerts/alert.html +4 -0
- umap/templates/umap/css.html +3 -0
- umap/templates/umap/js.html +2 -0
- umap/templates/umap/map_init.html +2 -0
- umap/templates/umap/user_dashboard.html +2 -0
- umap/tests/fixtures/test_upload_simple_marker.json +19 -0
- umap/tests/integration/conftest.py +3 -3
- umap/tests/integration/test_edit_datalayer.py +11 -0
- umap/tests/integration/test_import.py +20 -1
- umap/tests/integration/test_websocket_sync.py +69 -0
- umap/tests/test_dashboard.py +82 -0
- umap/tests/test_team_views.py +35 -1
- umap/tests/test_views.py +0 -74
- umap/views.py +5 -1
- umap/websocket_server.py +8 -1
- {umap_project-2.8.0a2.dist-info → umap_project-2.8.1.dist-info}/METADATA +5 -5
- {umap_project-2.8.0a2.dist-info → umap_project-2.8.1.dist-info}/RECORD +134 -127
- {umap_project-2.8.0a2.dist-info → umap_project-2.8.1.dist-info}/WHEEL +0 -0
- {umap_project-2.8.0a2.dist-info → umap_project-2.8.1.dist-info}/entry_points.txt +0 -0
- {umap_project-2.8.0a2.dist-info → umap_project-2.8.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -41,7 +41,7 @@ export class DataLayer extends ServerStored {
|
|
|
41
41
|
constructor(umap, leafletMap, data = {}) {
|
|
42
42
|
super()
|
|
43
43
|
this._umap = umap
|
|
44
|
-
this.sync = umap.
|
|
44
|
+
this.sync = umap.syncEngine.proxy(this)
|
|
45
45
|
this._index = Array()
|
|
46
46
|
this._features = {}
|
|
47
47
|
this._geojson = null
|
|
@@ -88,7 +88,6 @@ export class DataLayer extends ServerStored {
|
|
|
88
88
|
|
|
89
89
|
if (!this.createdOnServer) {
|
|
90
90
|
if (this.showAtLoad()) this.show()
|
|
91
|
-
this.isDirty = true
|
|
92
91
|
}
|
|
93
92
|
|
|
94
93
|
// Only layers that are displayed on load must be hidden/shown
|
|
@@ -151,7 +150,6 @@ export class DataLayer extends ServerStored {
|
|
|
151
150
|
for (const field of fields) {
|
|
152
151
|
this.layer.onEdit(field, builder)
|
|
153
152
|
}
|
|
154
|
-
this.redraw()
|
|
155
153
|
this.show()
|
|
156
154
|
break
|
|
157
155
|
case 'remote-data':
|
|
@@ -526,15 +524,13 @@ export class DataLayer extends ServerStored {
|
|
|
526
524
|
}
|
|
527
525
|
|
|
528
526
|
async importFromFiles(files, type) {
|
|
529
|
-
|
|
527
|
+
const toLoad = []
|
|
530
528
|
for (const file of files) {
|
|
531
|
-
|
|
532
|
-
if (features) {
|
|
533
|
-
all = all.concat(features)
|
|
534
|
-
}
|
|
529
|
+
toLoad.push(this.importFromFile(file, type))
|
|
535
530
|
}
|
|
531
|
+
const features = await Promise.all(toLoad)
|
|
536
532
|
return new Promise((resolve) => {
|
|
537
|
-
resolve(
|
|
533
|
+
resolve([].concat(...features))
|
|
538
534
|
})
|
|
539
535
|
}
|
|
540
536
|
|
|
@@ -594,7 +590,7 @@ export class DataLayer extends ServerStored {
|
|
|
594
590
|
options.name = translate('Clone of {name}', { name: this.options.name })
|
|
595
591
|
delete options.id
|
|
596
592
|
const geojson = Utils.CopyJSON(this._geojson)
|
|
597
|
-
const datalayer = this._umap.
|
|
593
|
+
const datalayer = this._umap.createDirtyDataLayer(options)
|
|
598
594
|
datalayer.fromGeoJSON(geojson)
|
|
599
595
|
return datalayer
|
|
600
596
|
}
|
|
@@ -613,7 +609,10 @@ export class DataLayer extends ServerStored {
|
|
|
613
609
|
}
|
|
614
610
|
|
|
615
611
|
reset() {
|
|
616
|
-
if (!this.createdOnServer)
|
|
612
|
+
if (!this.createdOnServer) {
|
|
613
|
+
this.erase()
|
|
614
|
+
return
|
|
615
|
+
}
|
|
617
616
|
|
|
618
617
|
this.resetOptions()
|
|
619
618
|
this.parentPane.appendChild(this.pane)
|
|
@@ -951,9 +950,13 @@ export class DataLayer extends ServerStored {
|
|
|
951
950
|
else this.hide()
|
|
952
951
|
}
|
|
953
952
|
|
|
954
|
-
zoomTo(
|
|
953
|
+
zoomTo() {
|
|
955
954
|
if (!this.isVisible()) return
|
|
956
|
-
bounds =
|
|
955
|
+
const bounds = this.layer.getBounds()
|
|
956
|
+
this.zoomToBounds(bounds)
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
zoomToBounds(bounds) {
|
|
957
960
|
if (bounds.isValid()) {
|
|
958
961
|
const options = { maxZoom: this.getOption('zoomTo') }
|
|
959
962
|
this._leafletMap.fitBounds(bounds, options)
|
|
@@ -1065,7 +1068,7 @@ export class DataLayer extends ServerStored {
|
|
|
1065
1068
|
|
|
1066
1069
|
setReferenceVersion({ response, sync }) {
|
|
1067
1070
|
this._referenceVersion = response.headers.get('X-Datalayer-Version')
|
|
1068
|
-
this.sync.update('_referenceVersion', this._referenceVersion)
|
|
1071
|
+
if (sync) this.sync.update('_referenceVersion', this._referenceVersion)
|
|
1069
1072
|
}
|
|
1070
1073
|
|
|
1071
1074
|
async save() {
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export default class DropControl {
|
|
2
|
+
constructor(umap, leafletMap, dropzone) {
|
|
3
|
+
this._umap = umap
|
|
4
|
+
this._leafletMap = leafletMap
|
|
5
|
+
this.dropzone = dropzone
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
enable() {
|
|
9
|
+
this.controller = new AbortController()
|
|
10
|
+
this.dropzone.addEventListener('dragenter', (e) => this.dragenter(e), {
|
|
11
|
+
signal: this.controller.signal,
|
|
12
|
+
})
|
|
13
|
+
this.dropzone.addEventListener('dragover', (e) => this.dragover(e), {
|
|
14
|
+
signal: this.controller.signal,
|
|
15
|
+
})
|
|
16
|
+
this.dropzone.addEventListener('drop', (e) => this.drop(e), {
|
|
17
|
+
signal: this.controller.signal,
|
|
18
|
+
})
|
|
19
|
+
this.dropzone.addEventListener('dragleave', (e) => this.dragleave(e), {
|
|
20
|
+
signal: this.controller.signal,
|
|
21
|
+
})
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
disable() {
|
|
25
|
+
this.controller.abort()
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
dragenter(event) {
|
|
29
|
+
event.stopPropagation()
|
|
30
|
+
event.preventDefault()
|
|
31
|
+
this._leafletMap.scrollWheelZoom.disable()
|
|
32
|
+
this.dropzone.classList.add('umap-dragover')
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
dragover(event) {
|
|
36
|
+
event.stopPropagation()
|
|
37
|
+
event.preventDefault()
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
drop(event) {
|
|
41
|
+
this._leafletMap.scrollWheelZoom.enable()
|
|
42
|
+
this.dropzone.classList.remove('umap-dragover')
|
|
43
|
+
event.stopPropagation()
|
|
44
|
+
event.preventDefault()
|
|
45
|
+
const importer = this._umap.importer
|
|
46
|
+
importer.build()
|
|
47
|
+
importer.files = event.dataTransfer.files
|
|
48
|
+
importer.submit()
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
dragleave() {
|
|
52
|
+
this._leafletMap.scrollWheelZoom.enable()
|
|
53
|
+
this.dropzone.classList.remove('umap-dragover')
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -64,7 +64,10 @@ export default class Importer extends Utils.WithTemplate {
|
|
|
64
64
|
this.TYPES = ['geojson', 'csv', 'gpx', 'kml', 'osm', 'georss', 'umap']
|
|
65
65
|
this.IMPORTERS = []
|
|
66
66
|
this.loadImporters()
|
|
67
|
-
this.dialog = new Dialog({
|
|
67
|
+
this.dialog = new Dialog({
|
|
68
|
+
className: 'importers dark',
|
|
69
|
+
back: () => this.showImporters(),
|
|
70
|
+
})
|
|
68
71
|
}
|
|
69
72
|
|
|
70
73
|
loadImporters() {
|
|
@@ -121,6 +124,11 @@ export default class Importer extends Utils.WithTemplate {
|
|
|
121
124
|
return this.qs('[type=file]').files
|
|
122
125
|
}
|
|
123
126
|
|
|
127
|
+
set files(files) {
|
|
128
|
+
this.qs('[type=file]').files = files
|
|
129
|
+
this.onFileChange()
|
|
130
|
+
}
|
|
131
|
+
|
|
124
132
|
get raw() {
|
|
125
133
|
return this.qs('textarea').value
|
|
126
134
|
}
|
|
@@ -158,7 +166,7 @@ export default class Importer extends Utils.WithTemplate {
|
|
|
158
166
|
get layer() {
|
|
159
167
|
return (
|
|
160
168
|
this._umap.datalayers[this.layerId] ||
|
|
161
|
-
this._umap.
|
|
169
|
+
this._umap.createDirtyDataLayer({ name: this.layerName })
|
|
162
170
|
)
|
|
163
171
|
}
|
|
164
172
|
|
|
@@ -172,7 +180,7 @@ export default class Importer extends Utils.WithTemplate {
|
|
|
172
180
|
button.addEventListener('click', () => plugin.open(this))
|
|
173
181
|
grid.appendChild(button)
|
|
174
182
|
}
|
|
175
|
-
this.dialog.open({ template: element, cancel: false, accept: false })
|
|
183
|
+
this.dialog.open({ template: element, cancel: false, accept: false, back: false })
|
|
176
184
|
}
|
|
177
185
|
|
|
178
186
|
build() {
|
|
@@ -210,11 +218,11 @@ export default class Importer extends Utils.WithTemplate {
|
|
|
210
218
|
this.qs('[name=submit').toggleAttribute('disabled', !this.canSubmit())
|
|
211
219
|
}
|
|
212
220
|
|
|
213
|
-
onFileChange(
|
|
221
|
+
onFileChange() {
|
|
214
222
|
let type = ''
|
|
215
223
|
let newType
|
|
216
|
-
for (const file of
|
|
217
|
-
newType =
|
|
224
|
+
for (const file of this.files) {
|
|
225
|
+
newType = Utils.detectFileType(file)
|
|
218
226
|
if (!type && newType) type = newType
|
|
219
227
|
if (type && newType !== type) {
|
|
220
228
|
type = ''
|
|
@@ -355,9 +363,11 @@ export default class Importer extends Utils.WithTemplate {
|
|
|
355
363
|
|
|
356
364
|
onSuccess(count) {
|
|
357
365
|
if (count) {
|
|
358
|
-
Alert.success(
|
|
359
|
-
count
|
|
360
|
-
|
|
366
|
+
Alert.success(
|
|
367
|
+
translate('Successfully imported {count} feature(s)', {
|
|
368
|
+
count: count,
|
|
369
|
+
})
|
|
370
|
+
)
|
|
361
371
|
} else {
|
|
362
372
|
Alert.success(translate('Data successfully imported!'))
|
|
363
373
|
}
|
|
@@ -377,7 +387,7 @@ export default class Importer extends Utils.WithTemplate {
|
|
|
377
387
|
bounds.extend(featureBounds)
|
|
378
388
|
}
|
|
379
389
|
this.onSuccess(features.length)
|
|
380
|
-
layer.
|
|
390
|
+
layer.zoomToBounds(bounds)
|
|
381
391
|
}
|
|
382
392
|
}
|
|
383
393
|
}
|
|
@@ -22,7 +22,7 @@ export function getClass(name) {
|
|
|
22
22
|
|
|
23
23
|
export const RECENT = []
|
|
24
24
|
|
|
25
|
-
const BaseIcon =
|
|
25
|
+
const BaseIcon = DivIcon.extend({
|
|
26
26
|
initialize: function (options) {
|
|
27
27
|
const default_options = {
|
|
28
28
|
iconSize: null, // Made in css
|
|
@@ -86,6 +86,7 @@ const DefaultIcon = BaseIcon.extend({
|
|
|
86
86
|
},
|
|
87
87
|
|
|
88
88
|
_setIconStyles: function (img, name) {
|
|
89
|
+
if (this.feature.isActive()) this.options.className += ' umap-icon-active'
|
|
89
90
|
BaseIcon.prototype._setIconStyles.call(this, img, name)
|
|
90
91
|
const color = this._getColor()
|
|
91
92
|
const opacity = this._getOpacity()
|
|
@@ -12,6 +12,7 @@ import { translate } from '../i18n.js'
|
|
|
12
12
|
import { uMapAlert as Alert } from '../../components/alerts/alert.js'
|
|
13
13
|
import * as Utils from '../utils.js'
|
|
14
14
|
import * as Icon from './icon.js'
|
|
15
|
+
import DropControl from '../drop.js'
|
|
15
16
|
|
|
16
17
|
// Those options are not saved on the server, so they can live here
|
|
17
18
|
// instead of in umap.properties
|
|
@@ -96,7 +97,7 @@ const ControlsMixin = {
|
|
|
96
97
|
this._controls.more = new U.MoreControls()
|
|
97
98
|
this._controls.scale = L.control.scale()
|
|
98
99
|
this._controls.permanentCredit = new U.PermanentCreditsControl(this)
|
|
99
|
-
this._umap.drop = new
|
|
100
|
+
this._umap.drop = new DropControl(this._umap, this, this._container)
|
|
100
101
|
this._controls.tilelayers = new U.TileLayerControl(this)
|
|
101
102
|
},
|
|
102
103
|
|
|
@@ -233,12 +234,8 @@ const ManageTilelayerMixin = {
|
|
|
233
234
|
},
|
|
234
235
|
|
|
235
236
|
updateTileLayers: function () {
|
|
236
|
-
const callback = (tilelayer) => {
|
|
237
|
-
this.options.tilelayer = tilelayer.toJSON()
|
|
238
|
-
this._umap.isDirty = true
|
|
239
|
-
}
|
|
240
237
|
if (this._controls.tilelayersChooser) {
|
|
241
|
-
this._controls.tilelayersChooser.openSwitcher({
|
|
238
|
+
this._controls.tilelayersChooser.openSwitcher({ edit: true })
|
|
242
239
|
}
|
|
243
240
|
},
|
|
244
241
|
}
|
|
@@ -263,8 +260,12 @@ export const LeafletMap = BaseMap.extend({
|
|
|
263
260
|
this.loader.onAdd(this)
|
|
264
261
|
|
|
265
262
|
if (!this.options.noControl) {
|
|
266
|
-
DomEvent.on(document.body, 'dataloading', (
|
|
267
|
-
|
|
263
|
+
DomEvent.on(document.body, 'dataloading', (event) =>
|
|
264
|
+
this.fire('dataloading', event.detail)
|
|
265
|
+
)
|
|
266
|
+
DomEvent.on(document.body, 'dataload', (event) =>
|
|
267
|
+
this.fire('dataload', event.detail)
|
|
268
|
+
)
|
|
268
269
|
this.on('click', this.closeInplaceToolbar)
|
|
269
270
|
}
|
|
270
271
|
|
|
@@ -21,23 +21,22 @@ export default function loadPopup(name) {
|
|
|
21
21
|
const Popup = BasePopup.extend({
|
|
22
22
|
initialize: function (feature) {
|
|
23
23
|
this.feature = feature
|
|
24
|
-
|
|
25
|
-
this.format()
|
|
26
|
-
BasePopup.prototype.initialize.call(this, {}, feature)
|
|
27
|
-
this.setContent(this.container)
|
|
24
|
+
BasePopup.prototype.initialize.call(this, {}, feature.ui)
|
|
28
25
|
},
|
|
29
26
|
|
|
30
|
-
|
|
27
|
+
loadContent: async function () {
|
|
28
|
+
const container = DomUtil.create('div', 'umap-popup')
|
|
31
29
|
const name = this.feature.getOption('popupTemplate')
|
|
32
|
-
this.content = loadTemplate(name, this.feature,
|
|
33
|
-
const elements =
|
|
30
|
+
this.content = await loadTemplate(name, this.feature, container)
|
|
31
|
+
const elements = container.querySelectorAll('img,iframe')
|
|
34
32
|
for (const element of elements) {
|
|
35
33
|
this.onElementLoaded(element)
|
|
36
34
|
}
|
|
37
|
-
if (!elements.length &&
|
|
38
|
-
|
|
39
|
-
DomUtil.add('h3', '',
|
|
35
|
+
if (!elements.length && container.textContent.replace('\n', '') === '') {
|
|
36
|
+
container.innerHTML = ''
|
|
37
|
+
DomUtil.add('h3', '', container, this.feature.getDisplayName())
|
|
40
38
|
}
|
|
39
|
+
this.setContent(container)
|
|
41
40
|
},
|
|
42
41
|
|
|
43
42
|
onElementLoaded: function (el) {
|
|
@@ -2,8 +2,9 @@ import { DomUtil, DomEvent } from '../../../vendors/leaflet/leaflet-src.esm.js'
|
|
|
2
2
|
import { translate, getLocale } from '../i18n.js'
|
|
3
3
|
import * as Utils from '../utils.js'
|
|
4
4
|
import * as Icon from './icon.js'
|
|
5
|
+
import { Request } from '../request.js'
|
|
5
6
|
|
|
6
|
-
export default function loadTemplate(name, feature, container) {
|
|
7
|
+
export default async function loadTemplate(name, feature, container) {
|
|
7
8
|
let klass = PopupTemplate
|
|
8
9
|
switch (name) {
|
|
9
10
|
case 'GeoRSSLink':
|
|
@@ -18,9 +19,12 @@ export default function loadTemplate(name, feature, container) {
|
|
|
18
19
|
case 'OSM':
|
|
19
20
|
klass = OSM
|
|
20
21
|
break
|
|
22
|
+
case 'Wikipedia':
|
|
23
|
+
klass = Wikipedia
|
|
24
|
+
break
|
|
21
25
|
}
|
|
22
26
|
const content = new klass()
|
|
23
|
-
return content.render(feature, container)
|
|
27
|
+
return await content.render(feature, container)
|
|
24
28
|
}
|
|
25
29
|
|
|
26
30
|
class PopupTemplate {
|
|
@@ -76,10 +80,10 @@ class PopupTemplate {
|
|
|
76
80
|
}
|
|
77
81
|
}
|
|
78
82
|
|
|
79
|
-
render(feature, container) {
|
|
83
|
+
async render(feature, container) {
|
|
80
84
|
const title = this.renderTitle(feature)
|
|
81
85
|
if (title) container.appendChild(title)
|
|
82
|
-
const body = this.renderBody(feature)
|
|
86
|
+
const body = await this.renderBody(feature)
|
|
83
87
|
if (body) DomUtil.add('div', 'umap-popup-content', container, body)
|
|
84
88
|
const footer = this.renderFooter(feature)
|
|
85
89
|
if (footer) container.appendChild(footer)
|
|
@@ -111,7 +115,7 @@ class Table extends TitleMixin(PopupTemplate) {
|
|
|
111
115
|
)
|
|
112
116
|
}
|
|
113
117
|
|
|
114
|
-
renderBody(feature) {
|
|
118
|
+
async renderBody(feature) {
|
|
115
119
|
const table = document.createElement('table')
|
|
116
120
|
|
|
117
121
|
for (const key in feature.properties) {
|
|
@@ -125,7 +129,7 @@ class Table extends TitleMixin(PopupTemplate) {
|
|
|
125
129
|
}
|
|
126
130
|
|
|
127
131
|
class GeoRSSImage extends TitleMixin(PopupTemplate) {
|
|
128
|
-
renderBody(feature) {
|
|
132
|
+
async renderBody(feature) {
|
|
129
133
|
const body = DomUtil.create('a')
|
|
130
134
|
body.href = feature.properties.link
|
|
131
135
|
body.target = '_blank'
|
|
@@ -142,7 +146,7 @@ class GeoRSSImage extends TitleMixin(PopupTemplate) {
|
|
|
142
146
|
}
|
|
143
147
|
|
|
144
148
|
class GeoRSSLink extends PopupTemplate {
|
|
145
|
-
renderBody(feature) {
|
|
149
|
+
async renderBody(feature) {
|
|
146
150
|
if (feature.properties.link) {
|
|
147
151
|
return Utils.loadTemplate(
|
|
148
152
|
`<a href="${feature.properties.link}" target="_blank"><h3>${feature.getDisplayName()}</h3></a>`
|
|
@@ -151,7 +155,7 @@ class GeoRSSLink extends PopupTemplate {
|
|
|
151
155
|
}
|
|
152
156
|
}
|
|
153
157
|
|
|
154
|
-
class OSM extends
|
|
158
|
+
class OSM extends PopupTemplate {
|
|
155
159
|
renderTitle(feature) {
|
|
156
160
|
const title = DomUtil.add('h3', 'popup-title')
|
|
157
161
|
const color = feature.getPreviewColor()
|
|
@@ -172,7 +176,7 @@ class OSM extends TitleMixin(PopupTemplate) {
|
|
|
172
176
|
return props.name
|
|
173
177
|
}
|
|
174
178
|
|
|
175
|
-
renderBody(feature) {
|
|
179
|
+
async renderBody(feature) {
|
|
176
180
|
const props = feature.properties
|
|
177
181
|
const body = document.createElement('div')
|
|
178
182
|
const locale = getLocale()
|
|
@@ -238,3 +242,43 @@ class OSM extends TitleMixin(PopupTemplate) {
|
|
|
238
242
|
return body
|
|
239
243
|
}
|
|
240
244
|
}
|
|
245
|
+
|
|
246
|
+
const _WIKIPEDIA_CACHE = {}
|
|
247
|
+
|
|
248
|
+
class Wikipedia extends PopupTemplate {
|
|
249
|
+
async callWikipedia(wikipedia) {
|
|
250
|
+
if (wikipedia && _WIKIPEDIA_CACHE[wikipedia]) return _WIKIPEDIA_CACHE[wikipedia]
|
|
251
|
+
// Wikipedia value should be in form of "{locale}:{title}", according to https://wiki.openstreetmap.org/wiki/Key:wikipedia
|
|
252
|
+
const [locale, page] = wikipedia.split(':')
|
|
253
|
+
const url = `https://${locale}.wikipedia.org/w/api.php?action=query&format=json&origin=*&pithumbsize=500&prop=extracts|pageimages&titles=${page}`
|
|
254
|
+
const request = new Request()
|
|
255
|
+
const response = await request.get(url)
|
|
256
|
+
if (response?.ok) {
|
|
257
|
+
const data = await response.json()
|
|
258
|
+
_WIKIPEDIA_CACHE[wikipedia] = data
|
|
259
|
+
return data
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
async renderBody(feature) {
|
|
264
|
+
const body = document.createElement('div')
|
|
265
|
+
const wikipedia = feature.properties.wikipedia
|
|
266
|
+
if (!wikipedia) return ''
|
|
267
|
+
const data = await this.callWikipedia(wikipedia)
|
|
268
|
+
if (data) {
|
|
269
|
+
const page = Object.values(data.query.pages)[0]
|
|
270
|
+
const title = page.title || feature.getDisplayName()
|
|
271
|
+
const extract = page.extract || ''
|
|
272
|
+
const thumbnail = page.thumbnail?.source
|
|
273
|
+
const [content, { image }] = Utils.loadTemplateWithRefs(
|
|
274
|
+
`<div><h3>${Utils.escapeHTML(title)}</h3><img data-ref="image" hidden src="" />${Utils.escapeHTML(extract)}</div>`
|
|
275
|
+
)
|
|
276
|
+
if (thumbnail) {
|
|
277
|
+
image.src = thumbnail
|
|
278
|
+
image.hidden = false
|
|
279
|
+
}
|
|
280
|
+
body.appendChild(content)
|
|
281
|
+
}
|
|
282
|
+
return body
|
|
283
|
+
}
|
|
284
|
+
}
|
|
@@ -238,11 +238,15 @@ export const LeafletMarker = Marker.extend({
|
|
|
238
238
|
},
|
|
239
239
|
|
|
240
240
|
highlight: function () {
|
|
241
|
-
|
|
241
|
+
this.feature.activate()
|
|
242
|
+
this._redraw()
|
|
243
|
+
this._bringToFront()
|
|
242
244
|
},
|
|
243
245
|
|
|
244
246
|
resetHighlight: function () {
|
|
245
|
-
|
|
247
|
+
this.feature.deactivate()
|
|
248
|
+
this._redraw()
|
|
249
|
+
this._resetZIndex()
|
|
246
250
|
},
|
|
247
251
|
|
|
248
252
|
getPopupToolbarAnchor: function () {
|
|
@@ -47,8 +47,8 @@ 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
|
-
fire(name,
|
|
51
|
-
document.body.dispatchEvent(new CustomEvent(name,
|
|
50
|
+
fire(name, detail) {
|
|
51
|
+
document.body.dispatchEvent(new CustomEvent(name, { detail }))
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
async _fetch(method, uri, headers, data) {
|
|
@@ -404,6 +404,7 @@ export const SCHEMA = {
|
|
|
404
404
|
['GeoRSSImage', translate('GeoRSS (title + image)')],
|
|
405
405
|
['GeoRSSLink', translate('GeoRSS (only link)')],
|
|
406
406
|
['OSM', translate('OpenStreetMap')],
|
|
407
|
+
['Wikipedia', translate('Wikipedia')],
|
|
407
408
|
],
|
|
408
409
|
default: 'Default',
|
|
409
410
|
},
|
|
@@ -3,6 +3,12 @@ import { HybridLogicalClock } from './hlc.js'
|
|
|
3
3
|
import { DataLayerUpdater, FeatureUpdater, MapUpdater } from './updaters.js'
|
|
4
4
|
import { WebSocketTransport } from './websocket.js'
|
|
5
5
|
|
|
6
|
+
// Start reconnecting after 2 seconds, then double the delay each time
|
|
7
|
+
// maxing out at 32 seconds.
|
|
8
|
+
const RECONNECT_DELAY = 2000
|
|
9
|
+
const RECONNECT_DELAY_FACTOR = 2
|
|
10
|
+
const MAX_RECONNECT_DELAY = 32000
|
|
11
|
+
|
|
6
12
|
/**
|
|
7
13
|
* The syncEngine exposes an API to sync messages between peers over the network.
|
|
8
14
|
*
|
|
@@ -42,32 +48,65 @@ import { WebSocketTransport } from './websocket.js'
|
|
|
42
48
|
* ```
|
|
43
49
|
*/
|
|
44
50
|
export class SyncEngine {
|
|
45
|
-
constructor(
|
|
51
|
+
constructor(umap) {
|
|
52
|
+
this._umap = umap
|
|
46
53
|
this.updaters = {
|
|
47
|
-
map: new MapUpdater(
|
|
48
|
-
feature: new FeatureUpdater(
|
|
49
|
-
datalayer: new DataLayerUpdater(
|
|
54
|
+
map: new MapUpdater(umap),
|
|
55
|
+
feature: new FeatureUpdater(umap),
|
|
56
|
+
datalayer: new DataLayerUpdater(umap),
|
|
50
57
|
}
|
|
51
58
|
this.transport = undefined
|
|
52
59
|
this._operations = new Operations()
|
|
60
|
+
|
|
61
|
+
this._reconnectTimeout = null
|
|
62
|
+
this._reconnectDelay = RECONNECT_DELAY
|
|
63
|
+
this.websocketConnected = false
|
|
53
64
|
}
|
|
54
65
|
|
|
55
|
-
async authenticate(
|
|
56
|
-
const
|
|
66
|
+
async authenticate() {
|
|
67
|
+
const websocketTokenURI = this._umap.urls.get('map_websocket_auth_token', {
|
|
68
|
+
map_id: this._umap.id,
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
const [response, _, error] = await this._umap.server.get(websocketTokenURI)
|
|
57
72
|
if (!error) {
|
|
58
|
-
this.start(
|
|
73
|
+
this.start(response.token)
|
|
59
74
|
}
|
|
60
75
|
}
|
|
61
76
|
|
|
62
|
-
start(
|
|
63
|
-
this.transport = new WebSocketTransport(
|
|
77
|
+
start(authToken) {
|
|
78
|
+
this.transport = new WebSocketTransport(
|
|
79
|
+
this._umap.properties.websocketURI,
|
|
80
|
+
authToken,
|
|
81
|
+
this
|
|
82
|
+
)
|
|
64
83
|
}
|
|
65
84
|
|
|
66
85
|
stop() {
|
|
67
|
-
if (this.transport)
|
|
86
|
+
if (this.transport) {
|
|
87
|
+
this.transport.close()
|
|
88
|
+
}
|
|
68
89
|
this.transport = undefined
|
|
69
90
|
}
|
|
70
91
|
|
|
92
|
+
onConnection() {
|
|
93
|
+
this._reconnectTimeout = null
|
|
94
|
+
this._reconnectDelay = RECONNECT_DELAY
|
|
95
|
+
this.websocketConnected = true
|
|
96
|
+
this.updaters.map.update({ key: 'numberOfConnectedPeers' })
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
reconnect() {
|
|
100
|
+
this.websocketConnected = false
|
|
101
|
+
this.updaters.map.update({ key: 'numberOfConnectedPeers' })
|
|
102
|
+
|
|
103
|
+
this._reconnectTimeout = setTimeout(() => {
|
|
104
|
+
if (this._reconnectDelay < MAX_RECONNECT_DELAY) {
|
|
105
|
+
this._reconnectDelay = this._reconnectDelay * RECONNECT_DELAY_FACTOR
|
|
106
|
+
}
|
|
107
|
+
this.authenticate()
|
|
108
|
+
}, this._reconnectDelay)
|
|
109
|
+
}
|
|
71
110
|
upsert(subject, metadata, value) {
|
|
72
111
|
this._send({ verb: 'upsert', subject, metadata, value })
|
|
73
112
|
}
|
|
@@ -183,9 +222,13 @@ export class SyncEngine {
|
|
|
183
222
|
* @param {string} payload.sender the uuid of the requesting peer
|
|
184
223
|
* @param {string} payload.latestKnownHLC the latest known HLC of the requesting peer
|
|
185
224
|
*/
|
|
186
|
-
onListOperationsRequest({ sender,
|
|
225
|
+
onListOperationsRequest({ sender, message }) {
|
|
226
|
+
debug(
|
|
227
|
+
`received operations request from peer ${sender} (since ${message.lastKnownHLC})`
|
|
228
|
+
)
|
|
229
|
+
|
|
187
230
|
this.sendToPeer(sender, 'ListOperationsResponse', {
|
|
188
|
-
operations: this._operations.getOperationsSince(lastKnownHLC),
|
|
231
|
+
operations: this._operations.getOperationsSince(message.lastKnownHLC),
|
|
189
232
|
})
|
|
190
233
|
}
|
|
191
234
|
|
|
@@ -446,5 +489,5 @@ export class Operations {
|
|
|
446
489
|
}
|
|
447
490
|
|
|
448
491
|
function debug(...args) {
|
|
449
|
-
console.debug('SYNC ⇆', ...args)
|
|
492
|
+
console.debug('SYNC ⇆', ...args.map((x) => JSON.stringify(x)))
|
|
450
493
|
}
|
|
@@ -54,7 +54,10 @@ export class MapUpdater extends BaseUpdater {
|
|
|
54
54
|
export class DataLayerUpdater extends BaseUpdater {
|
|
55
55
|
upsert({ value }) {
|
|
56
56
|
// Upsert only happens when a new datalayer is created.
|
|
57
|
-
this._umap.createDataLayer(value, false)
|
|
57
|
+
const datalayer = this._umap.createDataLayer(value, false)
|
|
58
|
+
// Prevent the layer to get data from the server, as it will get it
|
|
59
|
+
// from the sync.
|
|
60
|
+
datalayer._loaded = true
|
|
58
61
|
}
|
|
59
62
|
|
|
60
63
|
update({ key, metadata, value }) {
|
|
@@ -1,15 +1,59 @@
|
|
|
1
|
+
const PONG_TIMEOUT = 5000
|
|
2
|
+
const PING_INTERVAL = 30000
|
|
3
|
+
const FIRST_CONNECTION_TIMEOUT = 2000
|
|
4
|
+
|
|
1
5
|
export class WebSocketTransport {
|
|
2
6
|
constructor(webSocketURI, authToken, messagesReceiver) {
|
|
7
|
+
this.receiver = messagesReceiver
|
|
8
|
+
this.closeRequested = false
|
|
9
|
+
|
|
3
10
|
this.websocket = new WebSocket(webSocketURI)
|
|
11
|
+
|
|
4
12
|
this.websocket.onopen = () => {
|
|
5
13
|
this.send('JoinRequest', { token: authToken })
|
|
14
|
+
this.receiver.onConnection()
|
|
6
15
|
}
|
|
7
16
|
this.websocket.addEventListener('message', this.onMessage.bind(this))
|
|
8
|
-
this.
|
|
17
|
+
this.websocket.onclose = () => {
|
|
18
|
+
console.log('websocket closed')
|
|
19
|
+
if (!this.closeRequested) {
|
|
20
|
+
console.log('Not requested, reconnecting...')
|
|
21
|
+
this.receiver.reconnect()
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
this.ensureOpen = setInterval(() => {
|
|
26
|
+
if (this.websocket.readyState !== WebSocket.OPEN) {
|
|
27
|
+
this.websocket.close()
|
|
28
|
+
clearInterval(this.ensureOpen)
|
|
29
|
+
}
|
|
30
|
+
}, FIRST_CONNECTION_TIMEOUT)
|
|
31
|
+
|
|
32
|
+
// To ensure the connection is still alive, we send ping and expect pong back.
|
|
33
|
+
// Websocket provides a `ping` method to keep the connection alive, but it's
|
|
34
|
+
// unfortunately not possible to access it from the WebSocket object.
|
|
35
|
+
// See https://making.close.com/posts/reliable-websockets/ for more details.
|
|
36
|
+
this.pingInterval = setInterval(() => {
|
|
37
|
+
if (this.websocket.readyState === WebSocket.OPEN) {
|
|
38
|
+
this.websocket.send('ping')
|
|
39
|
+
this.pongReceived = false
|
|
40
|
+
setTimeout(() => {
|
|
41
|
+
if (!this.pongReceived) {
|
|
42
|
+
console.warn('No pong received, reconnecting...')
|
|
43
|
+
this.websocket.close()
|
|
44
|
+
clearInterval(this.pingInterval)
|
|
45
|
+
}
|
|
46
|
+
}, PONG_TIMEOUT)
|
|
47
|
+
}
|
|
48
|
+
}, PING_INTERVAL)
|
|
9
49
|
}
|
|
10
50
|
|
|
11
51
|
onMessage(wsMessage) {
|
|
12
|
-
|
|
52
|
+
if (wsMessage.data === 'pong') {
|
|
53
|
+
this.pongReceived = true
|
|
54
|
+
} else {
|
|
55
|
+
this.receiver.receive(JSON.parse(wsMessage.data))
|
|
56
|
+
}
|
|
13
57
|
}
|
|
14
58
|
|
|
15
59
|
send(kind, payload) {
|
|
@@ -20,6 +64,7 @@ export class WebSocketTransport {
|
|
|
20
64
|
}
|
|
21
65
|
|
|
22
66
|
close() {
|
|
67
|
+
this.closeRequested = true
|
|
23
68
|
this.websocket.close()
|
|
24
69
|
}
|
|
25
70
|
}
|