umap-project 2.8.2__py3-none-any.whl → 2.9.0b0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of umap-project might be problematic. Click here for more details.
- umap/__init__.py +1 -1
- umap/asgi.py +12 -7
- umap/context_processors.py +1 -0
- umap/locale/en/LC_MESSAGES/django.po +102 -59
- umap/locale/fr/LC_MESSAGES/django.mo +0 -0
- umap/locale/fr/LC_MESSAGES/django.po +105 -61
- umap/management/commands/empty_trash.py +12 -1
- umap/migrations/0026_datalayer_modified_at_datalayer_share_status.py +26 -0
- umap/models.py +23 -3
- umap/settings/base.py +4 -1
- umap/static/umap/base.css +1 -1
- umap/static/umap/content.css +2 -22
- umap/static/umap/css/bar.css +7 -10
- umap/static/umap/css/form.css +28 -29
- umap/static/umap/css/icon.css +8 -2
- umap/static/umap/css/panel.css +2 -1
- umap/static/umap/css/tooltip.css +33 -31
- umap/static/umap/img/16-white.svg +2 -0
- umap/static/umap/img/16.svg +1 -1
- umap/static/umap/img/providers/bitbucket.png +0 -0
- umap/static/umap/img/providers/github.png +0 -0
- umap/static/umap/img/providers/keycloak.png +0 -0
- umap/static/umap/img/providers/openstreetmap-oauth2.png +0 -0
- umap/static/umap/img/providers/twitter-oauth2.png +0 -0
- umap/static/umap/img/source/16-white.svg +3 -1
- umap/static/umap/img/source/16.svg +1 -1
- umap/static/umap/js/components/alerts/alert.js +4 -1
- umap/static/umap/js/modules/browser.js +6 -6
- umap/static/umap/js/modules/caption.js +30 -7
- umap/static/umap/js/modules/data/features.js +21 -24
- umap/static/umap/js/modules/data/layer.js +71 -33
- umap/static/umap/js/modules/form/builder.js +241 -0
- umap/static/umap/js/modules/form/fields.js +1338 -0
- umap/static/umap/js/modules/formatter.js +5 -8
- umap/static/umap/js/modules/help.js +3 -1
- umap/static/umap/js/modules/importer.js +1 -1
- umap/static/umap/js/modules/permissions.js +5 -4
- umap/static/umap/js/modules/rendering/icon.js +5 -1
- umap/static/umap/js/modules/rendering/layers/classified.js +11 -7
- umap/static/umap/js/modules/rendering/layers/cluster.js +11 -1
- umap/static/umap/js/modules/rendering/map.js +0 -2
- umap/static/umap/js/modules/rules.js +2 -1
- umap/static/umap/js/modules/schema.js +5 -6
- umap/static/umap/js/modules/share.js +3 -3
- umap/static/umap/js/modules/sync/engine.js +18 -13
- umap/static/umap/js/modules/sync/updaters.js +8 -0
- umap/static/umap/js/modules/sync/websocket.js +10 -5
- umap/static/umap/js/modules/tableeditor.js +3 -2
- umap/static/umap/js/modules/ui/bar.js +17 -9
- umap/static/umap/js/modules/ui/base.js +7 -24
- umap/static/umap/js/modules/ui/tooltip.js +19 -11
- umap/static/umap/js/modules/umap.js +36 -24
- umap/static/umap/js/modules/utils.js +196 -12
- umap/static/umap/js/umap.controls.js +0 -12
- umap/static/umap/locale/br.js +21 -13
- umap/static/umap/locale/br.json +21 -13
- umap/static/umap/locale/ca.js +12 -4
- umap/static/umap/locale/ca.json +12 -4
- umap/static/umap/locale/cs_CZ.js +10 -4
- umap/static/umap/locale/cs_CZ.json +10 -4
- umap/static/umap/locale/de.js +12 -4
- umap/static/umap/locale/de.json +12 -4
- umap/static/umap/locale/el.js +12 -4
- umap/static/umap/locale/el.json +12 -4
- umap/static/umap/locale/en.js +9 -4
- umap/static/umap/locale/en.json +9 -4
- umap/static/umap/locale/es.js +20 -12
- umap/static/umap/locale/es.json +20 -12
- umap/static/umap/locale/eu.js +12 -4
- umap/static/umap/locale/eu.json +12 -4
- umap/static/umap/locale/fa_IR.js +12 -4
- umap/static/umap/locale/fa_IR.json +12 -4
- umap/static/umap/locale/fr.js +10 -5
- umap/static/umap/locale/fr.json +10 -5
- umap/static/umap/locale/gl.js +353 -345
- umap/static/umap/locale/gl.json +353 -345
- umap/static/umap/locale/hu.js +9 -4
- umap/static/umap/locale/hu.json +9 -4
- umap/static/umap/locale/it.js +100 -92
- umap/static/umap/locale/it.json +100 -92
- umap/static/umap/locale/ms.js +12 -4
- umap/static/umap/locale/ms.json +12 -4
- umap/static/umap/locale/nl.js +12 -4
- umap/static/umap/locale/nl.json +12 -4
- umap/static/umap/locale/pl.js +12 -4
- umap/static/umap/locale/pl.json +12 -4
- umap/static/umap/locale/pt.js +12 -4
- umap/static/umap/locale/pt.json +12 -4
- umap/static/umap/locale/pt_PT.js +12 -4
- umap/static/umap/locale/pt_PT.json +12 -4
- umap/static/umap/locale/th_TH.js +12 -4
- umap/static/umap/locale/th_TH.json +12 -4
- umap/static/umap/locale/zh_TW.js +10 -4
- umap/static/umap/locale/zh_TW.json +10 -4
- umap/static/umap/map.css +12 -8
- umap/static/umap/nav.css +2 -3
- umap/static/umap/unittests/utils.js +14 -0
- umap/static/umap/vars.css +2 -0
- umap/sync/__init__.py +0 -0
- umap/sync/app.py +181 -0
- umap/sync/payloads.py +49 -0
- umap/templates/auth/user_detail.html +4 -0
- umap/templates/auth/user_form.html +9 -6
- umap/templates/auth/user_stars.html +4 -0
- umap/templates/base.html +1 -1
- umap/templates/registration/login.html +2 -5
- umap/templates/umap/about.html +5 -0
- umap/templates/umap/about_summary.html +2 -2
- umap/templates/umap/components/provider.html +8 -0
- umap/templates/umap/js.html +0 -3
- umap/templates/umap/map_detail.html +1 -1
- umap/templates/umap/password_change.html +4 -0
- umap/templates/umap/password_change_done.html +4 -0
- umap/templates/umap/search.html +4 -0
- umap/templates/umap/team_confirm_delete.html +4 -0
- umap/templates/umap/team_detail.html +4 -0
- umap/templates/umap/team_form.html +4 -0
- umap/templates/umap/user_dashboard.html +1 -1
- umap/templates/umap/user_teams.html +4 -0
- umap/tests/base.py +3 -1
- umap/tests/integration/conftest.py +16 -23
- umap/tests/integration/test_basics.py +2 -2
- umap/tests/integration/test_caption.py +1 -0
- umap/tests/integration/test_draw_polygon.py +3 -3
- umap/tests/integration/test_edit_datalayer.py +1 -1
- umap/tests/integration/test_edit_map.py +3 -3
- umap/tests/integration/test_edit_polygon.py +1 -1
- umap/tests/integration/test_import.py +23 -1
- umap/tests/integration/test_optimistic_merge.py +1 -0
- umap/tests/integration/test_picto.py +8 -8
- umap/tests/integration/test_save.py +1 -0
- umap/tests/integration/test_star.py +13 -9
- umap/tests/integration/test_tableeditor.py +1 -0
- umap/tests/integration/test_websocket_sync.py +112 -33
- umap/tests/settings.py +2 -0
- umap/tests/test_datalayer.py +2 -3
- umap/tests/test_datalayer_views.py +20 -1
- umap/tests/test_empty_trash.py +10 -3
- umap/tests/test_map_views.py +11 -0
- umap/utils.py +24 -11
- umap/views.py +37 -6
- {umap_project-2.8.2.dist-info → umap_project-2.9.0b0.dist-info}/METADATA +15 -15
- {umap_project-2.8.2.dist-info → umap_project-2.9.0b0.dist-info}/RECORD +146 -145
- {umap_project-2.8.2.dist-info → umap_project-2.9.0b0.dist-info}/WHEEL +1 -1
- umap/management/commands/run_websocket_server.py +0 -23
- umap/settings/local_s3.py +0 -45
- umap/static/umap/bitbucket.png +0 -0
- umap/static/umap/github.png +0 -0
- umap/static/umap/js/umap.forms.js +0 -1242
- umap/static/umap/keycloak.png +0 -0
- umap/static/umap/openstreetmap.png +0 -0
- umap/static/umap/twitter.png +0 -0
- umap/static/umap/vendors/formbuilder/Leaflet.FormBuilder.js +0 -468
- umap/tests/test_websocket_server.py +0 -22
- umap/websocket_server.py +0 -202
- {umap_project-2.8.2.dist-info → umap_project-2.9.0b0.dist-info}/entry_points.txt +0 -0
- {umap_project-2.8.2.dist-info → umap_project-2.9.0b0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/* Uses globals for: csv2geojson, osmtogeojson (not available as ESM) */
|
|
2
2
|
import { translate } from './i18n.js'
|
|
3
|
+
import { uMapAlert as Alert } from '../components/alerts/alert.js'
|
|
3
4
|
|
|
4
5
|
export const EXPORT_FORMATS = {
|
|
5
6
|
geojson: {
|
|
@@ -58,11 +59,7 @@ export class Formatter {
|
|
|
58
59
|
}
|
|
59
60
|
|
|
60
61
|
async fromGeoJSON(str) {
|
|
61
|
-
|
|
62
|
-
return JSON.parse(str)
|
|
63
|
-
} catch (err) {
|
|
64
|
-
U.Alert.error(`Invalid JSON file: ${err}`)
|
|
65
|
-
}
|
|
62
|
+
return JSON.parse(str)
|
|
66
63
|
}
|
|
67
64
|
|
|
68
65
|
async fromOSM(str) {
|
|
@@ -106,8 +103,8 @@ export class Formatter {
|
|
|
106
103
|
message: err[0].message,
|
|
107
104
|
})
|
|
108
105
|
}
|
|
109
|
-
|
|
110
|
-
console.
|
|
106
|
+
Alert.error(message, 10000)
|
|
107
|
+
console.debug(err)
|
|
111
108
|
}
|
|
112
109
|
if (result?.features.length) {
|
|
113
110
|
callback(result)
|
|
@@ -127,7 +124,7 @@ export class Formatter {
|
|
|
127
124
|
const doc = new DOMParser().parseFromString(x, 'text/xml')
|
|
128
125
|
const errorNode = doc.querySelector('parsererror')
|
|
129
126
|
if (errorNode) {
|
|
130
|
-
|
|
127
|
+
Alert.error(translate('Cannot parse data'))
|
|
131
128
|
}
|
|
132
129
|
return doc
|
|
133
130
|
}
|
|
@@ -228,7 +228,9 @@ export default class Help {
|
|
|
228
228
|
|
|
229
229
|
parse(container) {
|
|
230
230
|
for (const element of container.querySelectorAll('[data-help]')) {
|
|
231
|
-
|
|
231
|
+
if (element.dataset.help) {
|
|
232
|
+
this.button(element, element.dataset.help.split(','))
|
|
233
|
+
}
|
|
232
234
|
}
|
|
233
235
|
}
|
|
234
236
|
|
|
@@ -3,6 +3,7 @@ import { translate } from './i18n.js'
|
|
|
3
3
|
import { uMapAlert as Alert } from '../components/alerts/alert.js'
|
|
4
4
|
import { ServerStored } from './saving.js'
|
|
5
5
|
import * as Utils from './utils.js'
|
|
6
|
+
import { MutatingForm } from './form/builder.js'
|
|
6
7
|
|
|
7
8
|
// Dedicated object so we can deal with a separate dirty status, and thus
|
|
8
9
|
// call the endpoint only when needed, saving one call at each save.
|
|
@@ -58,7 +59,7 @@ export class MapPermissions extends ServerStored {
|
|
|
58
59
|
selectOptions: this._umap.properties.share_statuses,
|
|
59
60
|
},
|
|
60
61
|
])
|
|
61
|
-
const builder = new
|
|
62
|
+
const builder = new MutatingForm(this, fields)
|
|
62
63
|
const form = builder.build()
|
|
63
64
|
container.appendChild(form)
|
|
64
65
|
|
|
@@ -133,7 +134,7 @@ export class MapPermissions extends ServerStored {
|
|
|
133
134
|
{ handler: 'ManageEditors', label: translate("Map's editors") },
|
|
134
135
|
])
|
|
135
136
|
|
|
136
|
-
const builder = new
|
|
137
|
+
const builder = new MutatingForm(this, topFields)
|
|
137
138
|
const form = builder.build()
|
|
138
139
|
container.appendChild(form)
|
|
139
140
|
if (collaboratorsFields.length) {
|
|
@@ -141,7 +142,7 @@ export class MapPermissions extends ServerStored {
|
|
|
141
142
|
`<fieldset class="separator"><legend>${translate('Manage collaborators')}</legend></fieldset>`
|
|
142
143
|
)
|
|
143
144
|
container.appendChild(fieldset)
|
|
144
|
-
const builder = new
|
|
145
|
+
const builder = new MutatingForm(this, collaboratorsFields)
|
|
145
146
|
const form = builder.build()
|
|
146
147
|
container.appendChild(form)
|
|
147
148
|
}
|
|
@@ -269,7 +270,7 @@ export class DataLayerPermissions extends ServerStored {
|
|
|
269
270
|
},
|
|
270
271
|
],
|
|
271
272
|
]
|
|
272
|
-
const builder = new
|
|
273
|
+
const builder = new MutatingForm(this, fields, {
|
|
273
274
|
className: 'umap-form datalayer-permissions',
|
|
274
275
|
})
|
|
275
276
|
const form = builder.build()
|
|
@@ -70,6 +70,11 @@ const BaseIcon = DivIcon.extend({
|
|
|
70
70
|
},
|
|
71
71
|
|
|
72
72
|
onAdd: () => {},
|
|
73
|
+
|
|
74
|
+
_setIconStyles: function (img, name) {
|
|
75
|
+
if (this.feature.isActive()) this.options.className += ' umap-icon-active'
|
|
76
|
+
DivIcon.prototype._setIconStyles.call(this, img, name)
|
|
77
|
+
},
|
|
73
78
|
})
|
|
74
79
|
|
|
75
80
|
const DefaultIcon = BaseIcon.extend({
|
|
@@ -86,7 +91,6 @@ const DefaultIcon = BaseIcon.extend({
|
|
|
86
91
|
},
|
|
87
92
|
|
|
88
93
|
_setIconStyles: function (img, name) {
|
|
89
|
-
if (this.feature.isActive()) this.options.className += ' umap-icon-active'
|
|
90
94
|
BaseIcon.prototype._setIconStyles.call(this, img, name)
|
|
91
95
|
const color = this._getColor()
|
|
92
96
|
const opacity = this._getOpacity()
|
|
@@ -88,7 +88,11 @@ const ClassifiedMixin = {
|
|
|
88
88
|
},
|
|
89
89
|
|
|
90
90
|
getColorSchemes: function (classes) {
|
|
91
|
-
|
|
91
|
+
const found = this.colorSchemes.filter((scheme) =>
|
|
92
|
+
Boolean(colorbrewer[scheme][classes])
|
|
93
|
+
)
|
|
94
|
+
if (found.length) return found
|
|
95
|
+
return [['', translate('Default')]]
|
|
92
96
|
},
|
|
93
97
|
}
|
|
94
98
|
|
|
@@ -191,7 +195,7 @@ export const Choropleth = FeatureGroup.extend({
|
|
|
191
195
|
'options.choropleth.property',
|
|
192
196
|
{
|
|
193
197
|
handler: 'Select',
|
|
194
|
-
selectOptions: this.datalayer.
|
|
198
|
+
selectOptions: this.datalayer.allProperties(),
|
|
195
199
|
label: translate('Choropleth property value'),
|
|
196
200
|
},
|
|
197
201
|
],
|
|
@@ -300,7 +304,7 @@ export const Circles = FeatureGroup.extend({
|
|
|
300
304
|
'options.circles.property',
|
|
301
305
|
{
|
|
302
306
|
handler: 'Select',
|
|
303
|
-
selectOptions: this.datalayer.
|
|
307
|
+
selectOptions: this.datalayer.allProperties(),
|
|
304
308
|
label: translate('Property name to compute circles'),
|
|
305
309
|
},
|
|
306
310
|
],
|
|
@@ -377,7 +381,7 @@ export const Categorized = FeatureGroup.extend({
|
|
|
377
381
|
|
|
378
382
|
_getValue: function (feature) {
|
|
379
383
|
const key =
|
|
380
|
-
this.datalayer.options.categorized.property || this.datalayer.
|
|
384
|
+
this.datalayer.options.categorized.property || this.datalayer.allProperties()[0]
|
|
381
385
|
return feature.properties[key]
|
|
382
386
|
},
|
|
383
387
|
|
|
@@ -420,7 +424,7 @@ export const Categorized = FeatureGroup.extend({
|
|
|
420
424
|
} else {
|
|
421
425
|
this.options.colors = colorbrewer?.Accent[this._classes]
|
|
422
426
|
? colorbrewer?.Accent[this._classes]
|
|
423
|
-
:
|
|
427
|
+
: Utils.COLORS
|
|
424
428
|
}
|
|
425
429
|
},
|
|
426
430
|
|
|
@@ -430,7 +434,7 @@ export const Categorized = FeatureGroup.extend({
|
|
|
430
434
|
'options.categorized.property',
|
|
431
435
|
{
|
|
432
436
|
handler: 'Select',
|
|
433
|
-
selectOptions: this.datalayer.
|
|
437
|
+
selectOptions: this.datalayer.allProperties(),
|
|
434
438
|
label: translate('Category property'),
|
|
435
439
|
},
|
|
436
440
|
],
|
|
@@ -464,7 +468,7 @@ export const Categorized = FeatureGroup.extend({
|
|
|
464
468
|
|
|
465
469
|
onEdit: function (field, builder) {
|
|
466
470
|
// Only compute the categories if we're dealing with categorized
|
|
467
|
-
if (!field.startsWith('options.categorized')) return
|
|
471
|
+
if (!field.startsWith('options.categorized') && field !== 'options.type') return
|
|
468
472
|
// If user touches the categories, then force manual mode
|
|
469
473
|
if (field === 'options.categorized.categories') {
|
|
470
474
|
this.datalayer.options.categorized.mode = 'manual'
|
|
@@ -63,7 +63,17 @@ export const Cluster = L.MarkerClusterGroup.extend({
|
|
|
63
63
|
|
|
64
64
|
addLayer: function (layer) {
|
|
65
65
|
this._layers.push(layer)
|
|
66
|
-
|
|
66
|
+
try {
|
|
67
|
+
return L.MarkerClusterGroup.prototype.addLayer.call(this, layer)
|
|
68
|
+
} catch (error) {
|
|
69
|
+
console.debug(error)
|
|
70
|
+
// Certainly a race condition when loading a clustered layer
|
|
71
|
+
// while zooming (this for example can happen at load, when the
|
|
72
|
+
// initial zoom is changed by uMap).
|
|
73
|
+
// FIXME: remove when this is merged:
|
|
74
|
+
// https://github.com/Leaflet/Leaflet.markercluster/pull/1048/files
|
|
75
|
+
return this
|
|
76
|
+
}
|
|
67
77
|
},
|
|
68
78
|
|
|
69
79
|
removeLayer: function (layer) {
|
|
@@ -32,7 +32,6 @@ const ControlsMixin = {
|
|
|
32
32
|
'locate',
|
|
33
33
|
'measure',
|
|
34
34
|
'editinosm',
|
|
35
|
-
'star',
|
|
36
35
|
'tilelayers',
|
|
37
36
|
],
|
|
38
37
|
|
|
@@ -84,7 +83,6 @@ const ControlsMixin = {
|
|
|
84
83
|
this._controls.search = new U.SearchControl()
|
|
85
84
|
this._controls.embed = new Control.Embed(this._umap)
|
|
86
85
|
this._controls.tilelayersChooser = new U.TileLayerChooser(this)
|
|
87
|
-
if (this.options.user?.id) this._controls.star = new U.StarControl(this._umap)
|
|
88
86
|
this._controls.editinosm = new Control.EditInOSM({
|
|
89
87
|
position: 'topleft',
|
|
90
88
|
widgetOptions: {
|
|
@@ -3,6 +3,7 @@ import { translate } from './i18n.js'
|
|
|
3
3
|
import * as Utils from './utils.js'
|
|
4
4
|
import { AutocompleteDatalist } from './autocomplete.js'
|
|
5
5
|
import Orderable from './orderable.js'
|
|
6
|
+
import { MutatingForm } from './form/builder.js'
|
|
6
7
|
|
|
7
8
|
const EMPTY_VALUES = ['', undefined, null]
|
|
8
9
|
|
|
@@ -129,7 +130,7 @@ class Rule {
|
|
|
129
130
|
'options.dashArray',
|
|
130
131
|
]
|
|
131
132
|
const container = DomUtil.create('div')
|
|
132
|
-
const builder = new
|
|
133
|
+
const builder = new MutatingForm(this, options)
|
|
133
134
|
const defaultShapeProperties = DomUtil.add('div', '', container)
|
|
134
135
|
defaultShapeProperties.appendChild(builder.build())
|
|
135
136
|
const autocomplete = new AutocompleteDatalist(builder.helpers.condition.input)
|
|
@@ -447,6 +447,11 @@ export const SCHEMA = {
|
|
|
447
447
|
label: translate('Display label'),
|
|
448
448
|
inheritable: true,
|
|
449
449
|
default: false,
|
|
450
|
+
choices: [
|
|
451
|
+
[true, translate('always')],
|
|
452
|
+
[false, translate('never')],
|
|
453
|
+
['null', translate('on hover')],
|
|
454
|
+
],
|
|
450
455
|
},
|
|
451
456
|
slideshow: {
|
|
452
457
|
type: Object,
|
|
@@ -478,12 +483,6 @@ export const SCHEMA = {
|
|
|
478
483
|
label: translate('Sort key'),
|
|
479
484
|
inheritable: true,
|
|
480
485
|
},
|
|
481
|
-
starControl: {
|
|
482
|
-
type: Boolean,
|
|
483
|
-
impacts: ['ui'],
|
|
484
|
-
nullable: true,
|
|
485
|
-
label: translate('Display the star map button'),
|
|
486
|
-
},
|
|
487
486
|
stroke: {
|
|
488
487
|
type: Boolean,
|
|
489
488
|
impacts: ['data'],
|
|
@@ -2,6 +2,7 @@ import { DomUtil } from '../../vendors/leaflet/leaflet-src.esm.js'
|
|
|
2
2
|
import { EXPORT_FORMATS } from './formatter.js'
|
|
3
3
|
import { translate } from './i18n.js'
|
|
4
4
|
import * as Utils from './utils.js'
|
|
5
|
+
import { MutatingForm } from './form/builder.js'
|
|
5
6
|
|
|
6
7
|
export default class Share {
|
|
7
8
|
constructor(umap) {
|
|
@@ -125,9 +126,8 @@ export default class Share {
|
|
|
125
126
|
exportUrl.value = window.location.protocol + iframeExporter.buildUrl()
|
|
126
127
|
}
|
|
127
128
|
buildIframeCode()
|
|
128
|
-
const builder = new
|
|
129
|
-
|
|
130
|
-
})
|
|
129
|
+
const builder = new MutatingForm(iframeExporter, UIFields)
|
|
130
|
+
builder.on('set', buildIframeCode)
|
|
131
131
|
const iframeOptions = DomUtil.createFieldset(
|
|
132
132
|
this.container,
|
|
133
133
|
translate('Embed and link options')
|
|
@@ -61,6 +61,8 @@ export class SyncEngine {
|
|
|
61
61
|
this._reconnectTimeout = null
|
|
62
62
|
this._reconnectDelay = RECONNECT_DELAY
|
|
63
63
|
this.websocketConnected = false
|
|
64
|
+
this.closeRequested = false
|
|
65
|
+
this.peerId = Utils.generateId()
|
|
64
66
|
}
|
|
65
67
|
|
|
66
68
|
async authenticate() {
|
|
@@ -75,10 +77,14 @@ export class SyncEngine {
|
|
|
75
77
|
}
|
|
76
78
|
|
|
77
79
|
start(authToken) {
|
|
80
|
+
const path = this._umap.urls.get('ws_sync', { map_id: this._umap.id })
|
|
81
|
+
const protocol = window.location.protocol === 'http:' ? 'ws:' : 'wss:'
|
|
78
82
|
this.transport = new WebSocketTransport(
|
|
79
|
-
|
|
83
|
+
`${protocol}//${window.location.host}${path}`,
|
|
80
84
|
authToken,
|
|
81
|
-
this
|
|
85
|
+
this,
|
|
86
|
+
this.peerId,
|
|
87
|
+
this._umap.properties.user?.name
|
|
82
88
|
)
|
|
83
89
|
}
|
|
84
90
|
|
|
@@ -124,7 +130,7 @@ export class SyncEngine {
|
|
|
124
130
|
|
|
125
131
|
if (this.offline) return
|
|
126
132
|
if (this.transport) {
|
|
127
|
-
this.transport.send('OperationMessage', message)
|
|
133
|
+
this.transport.send('OperationMessage', { sender: this.peerId, ...message })
|
|
128
134
|
}
|
|
129
135
|
}
|
|
130
136
|
|
|
@@ -140,9 +146,8 @@ export class SyncEngine {
|
|
|
140
146
|
updater.applyMessage(operation)
|
|
141
147
|
}
|
|
142
148
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
return 0
|
|
149
|
+
getPeers() {
|
|
150
|
+
return this.peers || {}
|
|
146
151
|
}
|
|
147
152
|
|
|
148
153
|
/**
|
|
@@ -176,6 +181,7 @@ export class SyncEngine {
|
|
|
176
181
|
* @param {Object} payload
|
|
177
182
|
*/
|
|
178
183
|
onOperationMessage(payload) {
|
|
184
|
+
if (payload.sender === this.peerId) return
|
|
179
185
|
this._operations.storeRemoteOperations([payload])
|
|
180
186
|
this._applyOperation(payload)
|
|
181
187
|
}
|
|
@@ -187,9 +193,8 @@ export class SyncEngine {
|
|
|
187
193
|
* @param {string} payload.uuid The server-assigned uuid for this peer
|
|
188
194
|
* @param {string[]} payload.peers The list of peers uuids
|
|
189
195
|
*/
|
|
190
|
-
onJoinResponse({
|
|
191
|
-
debug('received join response', {
|
|
192
|
-
this.uuid = uuid
|
|
196
|
+
onJoinResponse({ peer, peers }) {
|
|
197
|
+
debug('received join response', { peer, peers })
|
|
193
198
|
this.onListPeersResponse({ peers })
|
|
194
199
|
|
|
195
200
|
// Get one peer at random
|
|
@@ -210,7 +215,7 @@ export class SyncEngine {
|
|
|
210
215
|
* @param {string[]} payload.peers The list of peers uuids
|
|
211
216
|
*/
|
|
212
217
|
onListPeersResponse({ peers }) {
|
|
213
|
-
debug('received peerinfo',
|
|
218
|
+
debug('received peerinfo', peers)
|
|
214
219
|
this.peers = peers
|
|
215
220
|
this.updaters.map.update({ key: 'numberOfConnectedPeers' })
|
|
216
221
|
}
|
|
@@ -285,7 +290,7 @@ export class SyncEngine {
|
|
|
285
290
|
sendToPeer(recipient, verb, payload) {
|
|
286
291
|
payload.verb = verb
|
|
287
292
|
this.transport.send('PeerMessage', {
|
|
288
|
-
sender: this.
|
|
293
|
+
sender: this.peerId,
|
|
289
294
|
recipient: recipient,
|
|
290
295
|
message: payload,
|
|
291
296
|
})
|
|
@@ -297,7 +302,7 @@ export class SyncEngine {
|
|
|
297
302
|
* @returns {string|bool} the selected peer uuid, or False if none was found.
|
|
298
303
|
*/
|
|
299
304
|
_getRandomPeer() {
|
|
300
|
-
const otherPeers = this.peers.filter((p) => p !== this.
|
|
305
|
+
const otherPeers = Object.keys(this.peers).filter((p) => p !== this.peerId)
|
|
301
306
|
if (otherPeers.length > 0) {
|
|
302
307
|
const random = Math.floor(Math.random() * otherPeers.length)
|
|
303
308
|
return otherPeers[random]
|
|
@@ -483,7 +488,7 @@ export class Operations {
|
|
|
483
488
|
return (
|
|
484
489
|
Utils.deepEqual(local.subject, remote.subject) &&
|
|
485
490
|
Utils.deepEqual(local.metadata, remote.metadata) &&
|
|
486
|
-
(!shouldCheckKey || (shouldCheckKey && local.key
|
|
491
|
+
(!shouldCheckKey || (shouldCheckKey && local.key === remote.key))
|
|
487
492
|
)
|
|
488
493
|
}
|
|
489
494
|
}
|
|
@@ -72,6 +72,14 @@ export class DataLayerUpdater extends BaseUpdater {
|
|
|
72
72
|
}
|
|
73
73
|
datalayer.render([key])
|
|
74
74
|
}
|
|
75
|
+
|
|
76
|
+
delete({ metadata }) {
|
|
77
|
+
const datalayer = this.getDataLayerFromID(metadata.id)
|
|
78
|
+
if (datalayer) {
|
|
79
|
+
datalayer.del(false)
|
|
80
|
+
datalayer.commitDelete()
|
|
81
|
+
}
|
|
82
|
+
}
|
|
75
83
|
}
|
|
76
84
|
|
|
77
85
|
export class FeatureUpdater extends BaseUpdater {
|
|
@@ -3,25 +3,28 @@ const PING_INTERVAL = 30000
|
|
|
3
3
|
const FIRST_CONNECTION_TIMEOUT = 2000
|
|
4
4
|
|
|
5
5
|
export class WebSocketTransport {
|
|
6
|
-
constructor(webSocketURI, authToken, messagesReceiver) {
|
|
6
|
+
constructor(webSocketURI, authToken, messagesReceiver, peerId, username) {
|
|
7
7
|
this.receiver = messagesReceiver
|
|
8
|
-
this.closeRequested = false
|
|
9
8
|
|
|
10
9
|
this.websocket = new WebSocket(webSocketURI)
|
|
11
10
|
|
|
12
11
|
this.websocket.onopen = () => {
|
|
13
|
-
this.send('JoinRequest', { token: authToken })
|
|
12
|
+
this.send('JoinRequest', { token: authToken, peer: peerId, username })
|
|
14
13
|
this.receiver.onConnection()
|
|
15
14
|
}
|
|
16
15
|
this.websocket.addEventListener('message', this.onMessage.bind(this))
|
|
17
16
|
this.websocket.onclose = () => {
|
|
18
17
|
console.log('websocket closed')
|
|
19
|
-
if (!this.closeRequested) {
|
|
18
|
+
if (!this.receiver.closeRequested) {
|
|
20
19
|
console.log('Not requested, reconnecting...')
|
|
21
20
|
this.receiver.reconnect()
|
|
22
21
|
}
|
|
23
22
|
}
|
|
24
23
|
|
|
24
|
+
this.websocket.onerror = (error) => {
|
|
25
|
+
console.log('WS ERROR', error)
|
|
26
|
+
}
|
|
27
|
+
|
|
25
28
|
this.ensureOpen = setInterval(() => {
|
|
26
29
|
if (this.websocket.readyState !== WebSocket.OPEN) {
|
|
27
30
|
this.websocket.close()
|
|
@@ -35,6 +38,7 @@ export class WebSocketTransport {
|
|
|
35
38
|
// See https://making.close.com/posts/reliable-websockets/ for more details.
|
|
36
39
|
this.pingInterval = setInterval(() => {
|
|
37
40
|
if (this.websocket.readyState === WebSocket.OPEN) {
|
|
41
|
+
console.log('sending ping')
|
|
38
42
|
this.websocket.send('ping')
|
|
39
43
|
this.pongReceived = false
|
|
40
44
|
setTimeout(() => {
|
|
@@ -64,7 +68,8 @@ export class WebSocketTransport {
|
|
|
64
68
|
}
|
|
65
69
|
|
|
66
70
|
close() {
|
|
67
|
-
|
|
71
|
+
console.log('Closing')
|
|
72
|
+
this.receiver.closeRequested = true
|
|
68
73
|
this.websocket.close()
|
|
69
74
|
}
|
|
70
75
|
}
|
|
@@ -2,6 +2,7 @@ import { DomEvent, DomUtil } from '../../vendors/leaflet/leaflet-src.esm.js'
|
|
|
2
2
|
import { translate } from './i18n.js'
|
|
3
3
|
import ContextMenu from './ui/contextmenu.js'
|
|
4
4
|
import { WithTemplate, loadTemplate } from './utils.js'
|
|
5
|
+
import { MutatingForm } from './form/builder.js'
|
|
5
6
|
|
|
6
7
|
const TEMPLATE = `
|
|
7
8
|
<table>
|
|
@@ -103,7 +104,7 @@ export default class TableEditor extends WithTemplate {
|
|
|
103
104
|
}
|
|
104
105
|
|
|
105
106
|
resetProperties() {
|
|
106
|
-
this.properties = this.datalayer.
|
|
107
|
+
this.properties = this.datalayer.allProperties()
|
|
107
108
|
if (this.properties.length === 0) {
|
|
108
109
|
this.properties = [U.DEFAULT_LABEL_KEY, 'description']
|
|
109
110
|
}
|
|
@@ -205,7 +206,7 @@ export default class TableEditor extends WithTemplate {
|
|
|
205
206
|
const tr = event.target.closest('tr')
|
|
206
207
|
const feature = this.datalayer.getFeatureById(tr.dataset.feature)
|
|
207
208
|
const handler = property === 'description' ? 'Textarea' : 'Input'
|
|
208
|
-
const builder = new
|
|
209
|
+
const builder = new MutatingForm(feature, [[field, { handler }]], {
|
|
209
210
|
id: `umap-feature-properties_${L.stamp(feature)}`,
|
|
210
211
|
})
|
|
211
212
|
cell.innerHTML = ''
|
|
@@ -2,13 +2,14 @@ import { DomEvent } from '../../../vendors/leaflet/leaflet-src.esm.js'
|
|
|
2
2
|
import { translate } from '../i18n.js'
|
|
3
3
|
import { WithTemplate } from '../utils.js'
|
|
4
4
|
import ContextMenu from './contextmenu.js'
|
|
5
|
+
import * as Utils from '../utils.js'
|
|
5
6
|
|
|
6
7
|
const TOP_BAR_TEMPLATE = `
|
|
7
8
|
<div class="umap-main-edit-toolbox with-transition dark">
|
|
8
9
|
<div class="umap-left-edit-toolbox" data-ref="left">
|
|
9
10
|
<div class="logo"><a class="" href="/" title="${translate('Go to the homepage')}">uMap</a></div>
|
|
10
|
-
<button class="map-name" type="button" data-ref="name"></button>
|
|
11
|
-
<button class="share-status" type="button" data-ref="share"></button>
|
|
11
|
+
<button class="map-name flat" type="button" data-ref="name"></button>
|
|
12
|
+
<button class="share-status flat" type="button" data-ref="share"></button>
|
|
12
13
|
</div>
|
|
13
14
|
<div class="umap-right-edit-toolbox" data-ref="right">
|
|
14
15
|
<button class="connected-peers round" type="button" data-ref="peers">
|
|
@@ -19,7 +20,7 @@ const TOP_BAR_TEMPLATE = `
|
|
|
19
20
|
<i class="icon icon-16 icon-profile"></i>
|
|
20
21
|
<span class="username" data-ref="username"></span>
|
|
21
22
|
</button>
|
|
22
|
-
<button class="umap-help-link" type="button" title="${translate('Help')}" data-ref="help">${translate('Help')}</button>
|
|
23
|
+
<button class="umap-help-link flat" type="button" title="${translate('Help')}" data-ref="help">${translate('Help')}</button>
|
|
23
24
|
<button class="edit-cancel round" type="button" data-ref="cancel">
|
|
24
25
|
<i class="icon icon-16 icon-restore"></i>
|
|
25
26
|
<span class="">${translate('Cancel edits')}</span>
|
|
@@ -96,17 +97,22 @@ export class TopBar extends WithTemplate {
|
|
|
96
97
|
}
|
|
97
98
|
})
|
|
98
99
|
|
|
99
|
-
const connectedPeers = this._umap.sync.getNumberOfConnectedPeers()
|
|
100
100
|
this.elements.peers.addEventListener('mouseover', () => {
|
|
101
|
-
|
|
101
|
+
const connectedPeers = this._umap.sync.getPeers()
|
|
102
|
+
if (!Object.keys(connectedPeers).length) return
|
|
103
|
+
const ul = Utils.loadTemplate(
|
|
104
|
+
`<ul>${Object.entries(connectedPeers)
|
|
105
|
+
.sort((el) => el !== this._umap.user?.name)
|
|
106
|
+
.map(([id, name]) => `<li>${name || translate('Anonymous')}</li>`)
|
|
107
|
+
.join('')}</ul>`
|
|
108
|
+
)
|
|
102
109
|
this._umap.tooltip.open({
|
|
103
|
-
content:
|
|
104
|
-
connectedPeers: connectedPeers,
|
|
105
|
-
}),
|
|
110
|
+
content: ul,
|
|
106
111
|
anchor: this.elements.peers,
|
|
107
112
|
position: 'bottom',
|
|
108
113
|
delay: 500,
|
|
109
114
|
duration: 5000,
|
|
115
|
+
accent: true,
|
|
110
116
|
})
|
|
111
117
|
})
|
|
112
118
|
|
|
@@ -145,7 +151,9 @@ export class TopBar extends WithTemplate {
|
|
|
145
151
|
}
|
|
146
152
|
|
|
147
153
|
redraw() {
|
|
148
|
-
|
|
154
|
+
const syncEnabled = this._umap.getProperty('syncEnabled')
|
|
155
|
+
this.elements.peers.hidden = !syncEnabled
|
|
156
|
+
this.elements.cancel.hidden = syncEnabled
|
|
149
157
|
this.elements.saveLabel.hidden = this._umap.permissions.isDraft()
|
|
150
158
|
this.elements.saveDraftLabel.hidden = !this._umap.permissions.isDraft()
|
|
151
159
|
}
|
|
@@ -2,27 +2,18 @@ export class Positioned {
|
|
|
2
2
|
openAt({ anchor, position }) {
|
|
3
3
|
if (anchor && position === 'top') {
|
|
4
4
|
this.anchorTop(anchor)
|
|
5
|
-
} else if (anchor && position === 'left') {
|
|
6
|
-
this.anchorLeft(anchor)
|
|
7
5
|
} else if (anchor && position === 'bottom') {
|
|
8
6
|
this.anchorBottom(anchor)
|
|
9
|
-
} else {
|
|
10
|
-
this.anchorAbsolute()
|
|
11
7
|
}
|
|
12
8
|
}
|
|
13
9
|
|
|
14
|
-
|
|
15
|
-
this.container.
|
|
16
|
-
|
|
17
|
-
this.parent.offsetLeft +
|
|
18
|
-
this.parent.clientWidth / 2 -
|
|
19
|
-
this.container.clientWidth / 2
|
|
20
|
-
const top = this.parent.offsetTop + 75
|
|
21
|
-
this.setPosition({ top: top, left: left })
|
|
10
|
+
toggleClassPosition(position) {
|
|
11
|
+
this.container.classList.toggle('tooltip-bottom', position === 'bottom')
|
|
12
|
+
this.container.classList.toggle('tooltip-top', position === 'top')
|
|
22
13
|
}
|
|
23
14
|
|
|
24
15
|
anchorTop(el) {
|
|
25
|
-
this.
|
|
16
|
+
this.toggleClassPosition('top')
|
|
26
17
|
const coords = this.getPosition(el)
|
|
27
18
|
this.setPosition({
|
|
28
19
|
left: coords.left - 10,
|
|
@@ -31,23 +22,15 @@ export class Positioned {
|
|
|
31
22
|
}
|
|
32
23
|
|
|
33
24
|
anchorBottom(el) {
|
|
34
|
-
this.
|
|
25
|
+
this.toggleClassPosition('bottom')
|
|
35
26
|
const coords = this.getPosition(el)
|
|
27
|
+
const selfCoords = this.getPosition(this.container)
|
|
36
28
|
this.setPosition({
|
|
37
|
-
left: coords.left,
|
|
29
|
+
left: coords.left + coords.width / 2 - selfCoords.width / 2,
|
|
38
30
|
top: coords.bottom + 11,
|
|
39
31
|
})
|
|
40
32
|
}
|
|
41
33
|
|
|
42
|
-
anchorLeft(el) {
|
|
43
|
-
this.container.className = 'tooltip-left'
|
|
44
|
-
const coords = this.getPosition(el)
|
|
45
|
-
this.setPosition({
|
|
46
|
-
top: coords.top,
|
|
47
|
-
right: document.documentElement.offsetWidth - coords.left + 11,
|
|
48
|
-
})
|
|
49
|
-
}
|
|
50
|
-
|
|
51
34
|
getPosition(el) {
|
|
52
35
|
return el.getBoundingClientRect()
|
|
53
36
|
}
|
|
@@ -1,24 +1,32 @@
|
|
|
1
|
-
import { DomEvent
|
|
1
|
+
import { DomEvent } from '../../../vendors/leaflet/leaflet-src.esm.js'
|
|
2
2
|
import { translate } from '../i18n.js'
|
|
3
3
|
import { Positioned } from './base.js'
|
|
4
|
+
import * as Utils from '../utils.js'
|
|
4
5
|
|
|
5
6
|
export default class Tooltip extends Positioned {
|
|
6
7
|
constructor(parent) {
|
|
7
8
|
super()
|
|
8
9
|
this.parent = parent
|
|
9
|
-
this.container =
|
|
10
|
-
this.
|
|
10
|
+
this.container = Utils.loadTemplate('<div class="umap-tooltip-container"></div>')
|
|
11
|
+
this.parent.appendChild(this.container)
|
|
11
12
|
DomEvent.disableClickPropagation(this.container)
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
this.container.addEventListener('contextmenu', (event) => event.stopPropagation()) // Do not activate our custom context menu.
|
|
14
|
+
this.container.addEventListener('wheel', (event) => event.stopPropagation())
|
|
15
|
+
this.container.addEventListener('MozMousePixelScroll', (event) =>
|
|
16
|
+
event.stopPropagation()
|
|
17
|
+
)
|
|
15
18
|
}
|
|
16
19
|
|
|
17
20
|
open(opts) {
|
|
21
|
+
this.container.classList.toggle('tooltip-accent', Boolean(opts.accent))
|
|
18
22
|
const showIt = () => {
|
|
23
|
+
if (opts.content.nodeType === 1) {
|
|
24
|
+
this.container.appendChild(opts.content)
|
|
25
|
+
} else {
|
|
26
|
+
this.container.innerHTML = Utils.escapeHTML(opts.content)
|
|
27
|
+
}
|
|
28
|
+
this.parent.classList.add('umap-tooltip')
|
|
19
29
|
this.openAt(opts)
|
|
20
|
-
L.DomUtil.addClass(this.parent, 'umap-tooltip')
|
|
21
|
-
this.container.innerHTML = U.Utils.escapeHTML(opts.content)
|
|
22
30
|
}
|
|
23
31
|
this.TOOLTIP_ID = window.setTimeout(L.bind(showIt, this), opts.delay || 0)
|
|
24
32
|
const id = this.TOOLTIP_ID
|
|
@@ -26,7 +34,7 @@ export default class Tooltip extends Positioned {
|
|
|
26
34
|
this.close(id)
|
|
27
35
|
}
|
|
28
36
|
if (opts.anchor) {
|
|
29
|
-
|
|
37
|
+
opts.anchor.addEventListener('mouseout', closeIt, { once: true })
|
|
30
38
|
}
|
|
31
39
|
if (opts.duration !== Number.POSITIVE_INFINITY) {
|
|
32
40
|
window.setTimeout(closeIt, opts.duration || 3000)
|
|
@@ -38,9 +46,9 @@ export default class Tooltip extends Positioned {
|
|
|
38
46
|
// in the meantime. Eg. after a mouseout from the anchor.
|
|
39
47
|
window.clearTimeout(id)
|
|
40
48
|
if (id && id !== this.TOOLTIP_ID) return
|
|
41
|
-
this.
|
|
49
|
+
this.toggleClassPosition()
|
|
42
50
|
this.container.innerHTML = ''
|
|
43
51
|
this.setPosition({})
|
|
44
|
-
|
|
52
|
+
this.parent.classList.remove('umap-tooltip')
|
|
45
53
|
}
|
|
46
54
|
}
|