umap-project 2.8.0b0__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/static/umap/content.css +18 -13
- umap/static/umap/css/bar.css +4 -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 +8 -5
- umap/static/umap/js/modules/data/layer.js +13 -8
- umap/static/umap/js/modules/drop.js +55 -0
- umap/static/umap/js/modules/importer.js +10 -5
- umap/static/umap/js/modules/rendering/map.js +2 -1
- 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/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/umap.js +37 -21
- umap/static/umap/js/modules/utils.js +2 -0
- umap/static/umap/js/umap.controls.js +1 -51
- 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/unittests/sync.js +4 -1
- umap/templates/403.html +12 -0
- umap/templates/404.html +4 -13
- umap/templates/40x.html +9 -0
- umap/tests/integration/conftest.py +3 -3
- umap/tests/integration/test_websocket_sync.py +69 -0
- umap/websocket_server.py +8 -1
- {umap_project-2.8.0b0.dist-info → umap_project-2.8.1.dist-info}/METADATA +5 -5
- {umap_project-2.8.0b0.dist-info → umap_project-2.8.1.dist-info}/RECORD +112 -108
- {umap_project-2.8.0b0.dist-info → umap_project-2.8.1.dist-info}/WHEEL +0 -0
- {umap_project-2.8.0b0.dist-info → umap_project-2.8.1.dist-info}/entry_points.txt +0 -0
- {umap_project-2.8.0b0.dist-info → umap_project-2.8.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -124,6 +124,11 @@ export default class Importer extends Utils.WithTemplate {
|
|
|
124
124
|
return this.qs('[type=file]').files
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
+
set files(files) {
|
|
128
|
+
this.qs('[type=file]').files = files
|
|
129
|
+
this.onFileChange()
|
|
130
|
+
}
|
|
131
|
+
|
|
127
132
|
get raw() {
|
|
128
133
|
return this.qs('textarea').value
|
|
129
134
|
}
|
|
@@ -161,7 +166,7 @@ export default class Importer extends Utils.WithTemplate {
|
|
|
161
166
|
get layer() {
|
|
162
167
|
return (
|
|
163
168
|
this._umap.datalayers[this.layerId] ||
|
|
164
|
-
this._umap.
|
|
169
|
+
this._umap.createDirtyDataLayer({ name: this.layerName })
|
|
165
170
|
)
|
|
166
171
|
}
|
|
167
172
|
|
|
@@ -213,11 +218,11 @@ export default class Importer extends Utils.WithTemplate {
|
|
|
213
218
|
this.qs('[name=submit').toggleAttribute('disabled', !this.canSubmit())
|
|
214
219
|
}
|
|
215
220
|
|
|
216
|
-
onFileChange(
|
|
221
|
+
onFileChange() {
|
|
217
222
|
let type = ''
|
|
218
223
|
let newType
|
|
219
|
-
for (const file of
|
|
220
|
-
newType =
|
|
224
|
+
for (const file of this.files) {
|
|
225
|
+
newType = Utils.detectFileType(file)
|
|
221
226
|
if (!type && newType) type = newType
|
|
222
227
|
if (type && newType !== type) {
|
|
223
228
|
type = ''
|
|
@@ -382,7 +387,7 @@ export default class Importer extends Utils.WithTemplate {
|
|
|
382
387
|
bounds.extend(featureBounds)
|
|
383
388
|
}
|
|
384
389
|
this.onSuccess(features.length)
|
|
385
|
-
layer.
|
|
390
|
+
layer.zoomToBounds(bounds)
|
|
386
391
|
}
|
|
387
392
|
}
|
|
388
393
|
}
|
|
@@ -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
|
|
|
@@ -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
|
+
}
|
|
@@ -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
|
}
|
|
@@ -110,7 +110,7 @@ export class TopBar extends WithTemplate {
|
|
|
110
110
|
})
|
|
111
111
|
})
|
|
112
112
|
|
|
113
|
-
this.elements.help.addEventListener('click', () => this._umap.showGetStarted())
|
|
113
|
+
this.elements.help.addEventListener('click', () => this._umap.help.showGetStarted())
|
|
114
114
|
this.elements.cancel.addEventListener('click', () => this._umap.askForReset())
|
|
115
115
|
this.elements.cancel.addEventListener('mouseover', () => {
|
|
116
116
|
this._umap.tooltip.open({
|
|
@@ -61,8 +61,6 @@ export default class Umap extends ServerStored {
|
|
|
61
61
|
)
|
|
62
62
|
this.searchParams = new URLSearchParams(window.location.search)
|
|
63
63
|
|
|
64
|
-
this.sync_engine = new SyncEngine(this)
|
|
65
|
-
this.sync = this.sync_engine.proxy(this)
|
|
66
64
|
// Locale name (pt_PT, en_US…)
|
|
67
65
|
// To be used for Django localization
|
|
68
66
|
if (geojson.properties.locale) setLocale(geojson.properties.locale)
|
|
@@ -124,6 +122,9 @@ export default class Umap extends ServerStored {
|
|
|
124
122
|
this.share = new Share(this)
|
|
125
123
|
this.rules = new Rules(this)
|
|
126
124
|
|
|
125
|
+
this.syncEngine = new SyncEngine(this)
|
|
126
|
+
this.sync = this.syncEngine.proxy(this)
|
|
127
|
+
|
|
127
128
|
if (this.hasEditMode()) {
|
|
128
129
|
this.editPanel = new EditPanel(this, this._leafletMap)
|
|
129
130
|
this.fullPanel = new FullPanel(this, this._leafletMap)
|
|
@@ -323,14 +324,14 @@ export default class Umap extends ServerStored {
|
|
|
323
324
|
dataUrl = decodeURIComponent(dataUrl)
|
|
324
325
|
dataUrl = this.renderUrl(dataUrl)
|
|
325
326
|
dataUrl = this.proxyUrl(dataUrl)
|
|
326
|
-
const datalayer = this.
|
|
327
|
+
const datalayer = this.createDirtyDataLayer()
|
|
327
328
|
await datalayer
|
|
328
329
|
.importFromUrl(dataUrl, dataFormat)
|
|
329
330
|
.then(() => datalayer.zoomTo())
|
|
330
331
|
}
|
|
331
332
|
} else if (data) {
|
|
332
333
|
data = decodeURIComponent(data)
|
|
333
|
-
const datalayer = this.
|
|
334
|
+
const datalayer = this.createDirtyDataLayer()
|
|
334
335
|
await datalayer.importRaw(data, dataFormat).then(() => datalayer.zoomTo())
|
|
335
336
|
}
|
|
336
337
|
}
|
|
@@ -580,9 +581,13 @@ export default class Umap extends ServerStored {
|
|
|
580
581
|
this.fire('datalayersloaded')
|
|
581
582
|
const toLoad = []
|
|
582
583
|
for (const datalayer of this.datalayersIndex) {
|
|
583
|
-
if (datalayer.showAtLoad()) toLoad.push(datalayer.show())
|
|
584
|
+
if (datalayer.showAtLoad()) toLoad.push(() => datalayer.show())
|
|
585
|
+
}
|
|
586
|
+
while (toLoad.length) {
|
|
587
|
+
const chunk = toLoad.splice(0, 10)
|
|
588
|
+
await Promise.all(chunk.map((func) => func()))
|
|
584
589
|
}
|
|
585
|
-
|
|
590
|
+
|
|
586
591
|
this.dataloaded = true
|
|
587
592
|
this.fire('dataloaded')
|
|
588
593
|
}
|
|
@@ -598,8 +603,14 @@ export default class Umap extends ServerStored {
|
|
|
598
603
|
return datalayer
|
|
599
604
|
}
|
|
600
605
|
|
|
606
|
+
createDirtyDataLayer(options) {
|
|
607
|
+
const datalayer = this.createDataLayer(options, true)
|
|
608
|
+
datalayer.isDirty = true
|
|
609
|
+
return datalayer
|
|
610
|
+
}
|
|
611
|
+
|
|
601
612
|
newDataLayer() {
|
|
602
|
-
const datalayer = this.
|
|
613
|
+
const datalayer = this.createDirtyDataLayer({})
|
|
603
614
|
datalayer.edit()
|
|
604
615
|
}
|
|
605
616
|
|
|
@@ -1117,8 +1128,8 @@ export default class Umap extends ServerStored {
|
|
|
1117
1128
|
}
|
|
1118
1129
|
this.ensurePanesOrder()
|
|
1119
1130
|
this._leafletMap.initTileLayers()
|
|
1120
|
-
this.isDirty = false
|
|
1121
1131
|
this.onDataLayersChanged()
|
|
1132
|
+
this.isDirty = !this.id
|
|
1122
1133
|
}
|
|
1123
1134
|
|
|
1124
1135
|
async save() {
|
|
@@ -1257,18 +1268,13 @@ export default class Umap extends ServerStored {
|
|
|
1257
1268
|
}
|
|
1258
1269
|
|
|
1259
1270
|
async initSyncEngine() {
|
|
1271
|
+
// this.properties.websocketEnabled is set by the server admin
|
|
1260
1272
|
if (this.properties.websocketEnabled === false) return
|
|
1273
|
+
// this.properties.syncEnabled is set by the user in the map settings
|
|
1261
1274
|
if (this.properties.syncEnabled !== true) {
|
|
1262
1275
|
this.sync.stop()
|
|
1263
1276
|
} else {
|
|
1264
|
-
|
|
1265
|
-
map_id: this.id,
|
|
1266
|
-
})
|
|
1267
|
-
await this.sync.authenticate(
|
|
1268
|
-
ws_token_uri,
|
|
1269
|
-
this.properties.websocketURI,
|
|
1270
|
-
this.server
|
|
1271
|
-
)
|
|
1277
|
+
await this.sync.authenticate()
|
|
1272
1278
|
}
|
|
1273
1279
|
}
|
|
1274
1280
|
|
|
@@ -1343,7 +1349,17 @@ export default class Umap extends ServerStored {
|
|
|
1343
1349
|
},
|
|
1344
1350
|
numberOfConnectedPeers: () => {
|
|
1345
1351
|
Utils.eachElement('.connected-peers span', (el) => {
|
|
1346
|
-
|
|
1352
|
+
if (this.sync.websocketConnected) {
|
|
1353
|
+
el.textContent = this.sync.getNumberOfConnectedPeers()
|
|
1354
|
+
} else {
|
|
1355
|
+
el.textContent = translate('Disconnected')
|
|
1356
|
+
}
|
|
1357
|
+
el.parentElement.classList.toggle('off', !this.sync.websocketConnected)
|
|
1358
|
+
})
|
|
1359
|
+
},
|
|
1360
|
+
'properties.starred': () => {
|
|
1361
|
+
Utils.eachElement('.map-star', (el) => {
|
|
1362
|
+
el.classList.toggle('starred', this.properties.starred)
|
|
1347
1363
|
})
|
|
1348
1364
|
},
|
|
1349
1365
|
}
|
|
@@ -1383,7 +1399,7 @@ export default class Umap extends ServerStored {
|
|
|
1383
1399
|
fallback.show()
|
|
1384
1400
|
return fallback
|
|
1385
1401
|
}
|
|
1386
|
-
return this.
|
|
1402
|
+
return this.createDirtyDataLayer()
|
|
1387
1403
|
}
|
|
1388
1404
|
|
|
1389
1405
|
findDataLayer(method, context) {
|
|
@@ -1531,7 +1547,7 @@ export default class Umap extends ServerStored {
|
|
|
1531
1547
|
? translate('Map has been starred')
|
|
1532
1548
|
: translate('Map has been unstarred')
|
|
1533
1549
|
)
|
|
1534
|
-
this.render(['starred'])
|
|
1550
|
+
this.render(['properties.starred'])
|
|
1535
1551
|
}
|
|
1536
1552
|
|
|
1537
1553
|
processFileToImport(file, layer, type) {
|
|
@@ -1547,7 +1563,7 @@ export default class Umap extends ServerStored {
|
|
|
1547
1563
|
if (type === 'umap') {
|
|
1548
1564
|
this.importUmapFile(file, 'umap')
|
|
1549
1565
|
} else {
|
|
1550
|
-
if (!layer) layer = this.
|
|
1566
|
+
if (!layer) layer = this.createDirtyDataLayer({ name: file.name })
|
|
1551
1567
|
layer.importFromFile(file, type)
|
|
1552
1568
|
}
|
|
1553
1569
|
}
|
|
@@ -1573,7 +1589,7 @@ export default class Umap extends ServerStored {
|
|
|
1573
1589
|
delete geojson._storage
|
|
1574
1590
|
}
|
|
1575
1591
|
delete geojson._umap_options?.id // Never trust an id at this stage
|
|
1576
|
-
const dataLayer = this.
|
|
1592
|
+
const dataLayer = this.createDirtyDataLayer(geojson._umap_options)
|
|
1577
1593
|
dataLayer.fromUmapGeoJSON(geojson)
|
|
1578
1594
|
}
|
|
1579
1595
|
|
|
@@ -337,52 +337,6 @@ U.DrawToolbar = L.Toolbar.Control.extend({
|
|
|
337
337
|
},
|
|
338
338
|
})
|
|
339
339
|
|
|
340
|
-
U.DropControl = L.Class.extend({
|
|
341
|
-
initialize: function (map) {
|
|
342
|
-
this.map = map
|
|
343
|
-
this.dropzone = map._container
|
|
344
|
-
},
|
|
345
|
-
|
|
346
|
-
enable: function () {
|
|
347
|
-
L.DomEvent.on(this.dropzone, 'dragenter', this.dragenter, this)
|
|
348
|
-
L.DomEvent.on(this.dropzone, 'dragover', this.dragover, this)
|
|
349
|
-
L.DomEvent.on(this.dropzone, 'drop', this.drop, this)
|
|
350
|
-
L.DomEvent.on(this.dropzone, 'dragleave', this.dragleave, this)
|
|
351
|
-
},
|
|
352
|
-
|
|
353
|
-
disable: function () {
|
|
354
|
-
L.DomEvent.off(this.dropzone, 'dragenter', this.dragenter, this)
|
|
355
|
-
L.DomEvent.off(this.dropzone, 'dragover', this.dragover, this)
|
|
356
|
-
L.DomEvent.off(this.dropzone, 'drop', this.drop, this)
|
|
357
|
-
L.DomEvent.off(this.dropzone, 'dragleave', this.dragleave, this)
|
|
358
|
-
},
|
|
359
|
-
|
|
360
|
-
dragenter: function (event) {
|
|
361
|
-
L.DomEvent.stop(event)
|
|
362
|
-
this.map.scrollWheelZoom.disable()
|
|
363
|
-
this.dropzone.classList.add('umap-dragover')
|
|
364
|
-
},
|
|
365
|
-
|
|
366
|
-
dragover: (event) => {
|
|
367
|
-
L.DomEvent.stop(event)
|
|
368
|
-
},
|
|
369
|
-
|
|
370
|
-
drop: function (event) {
|
|
371
|
-
this.map.scrollWheelZoom.enable()
|
|
372
|
-
this.dropzone.classList.remove('umap-dragover')
|
|
373
|
-
L.DomEvent.stop(event)
|
|
374
|
-
for (const file of event.dataTransfer.files) {
|
|
375
|
-
this.map._umap.processFileToImport(file)
|
|
376
|
-
}
|
|
377
|
-
this.map._umap.onceDataLoaded(this.map._umap.fitDataBounds)
|
|
378
|
-
},
|
|
379
|
-
|
|
380
|
-
dragleave: function () {
|
|
381
|
-
this.map.scrollWheelZoom.enable()
|
|
382
|
-
this.dropzone.classList.remove('umap-dragover')
|
|
383
|
-
},
|
|
384
|
-
})
|
|
385
|
-
|
|
386
340
|
U.EditControl = L.Control.extend({
|
|
387
341
|
options: {
|
|
388
342
|
position: 'topright',
|
|
@@ -541,11 +495,7 @@ U.StarControl = L.Control.Button.extend({
|
|
|
541
495
|
options: {
|
|
542
496
|
position: 'topleft',
|
|
543
497
|
title: L._('Star this map'),
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
getClassName: function () {
|
|
547
|
-
const status = this._umap.properties.starred ? ' starred' : ''
|
|
548
|
-
return `leaflet-control-star umap-control${status}`
|
|
498
|
+
className: 'leaflet-control-star map-star umap-control',
|
|
549
499
|
},
|
|
550
500
|
|
|
551
501
|
onClick: function () {
|