umap-project 2.3.1__py3-none-any.whl → 2.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of umap-project might be problematic. Click here for more details.
- umap/__init__.py +1 -1
- umap/locale/en/LC_MESSAGES/django.po +81 -31
- umap/locale/fr/LC_MESSAGES/django.mo +0 -0
- umap/locale/fr/LC_MESSAGES/django.po +117 -66
- umap/management/commands/run_websocket_server.py +23 -0
- umap/models.py +6 -1
- umap/settings/base.py +11 -3
- umap/static/umap/base.css +64 -184
- umap/static/umap/content.css +3 -2
- umap/static/umap/css/dialog.css +18 -0
- umap/static/umap/css/icon.css +8 -0
- umap/static/umap/css/importers.css +51 -0
- umap/static/umap/css/panel.css +18 -57
- umap/static/umap/css/tooltip.css +59 -0
- umap/static/umap/css/window.css +35 -0
- umap/static/umap/img/16-white.svg +1 -3
- umap/static/umap/img/alert-icon-error.svg +8 -0
- umap/static/umap/img/alert-icon-info.svg +4 -0
- umap/static/umap/img/alert-icon-success.svg +3 -0
- umap/static/umap/img/icon-external-link.svg +3 -0
- umap/static/umap/img/importers/communesfr.svg +5 -0
- umap/static/umap/img/importers/datasets.svg +13 -0
- umap/static/umap/img/importers/geodatamine.svg +10 -0
- umap/static/umap/img/importers/overpass.svg +7 -0
- umap/static/umap/img/importers/random.svg +18 -0
- umap/static/umap/img/importers/random1.svg +4 -0
- umap/static/umap/img/importers/random2.svg +4 -0
- umap/static/umap/img/source/16-white.svg +2 -4
- umap/static/umap/js/components/alerts/alert.css +160 -0
- umap/static/umap/js/components/alerts/alert.js +169 -0
- umap/static/umap/js/components/base.js +54 -0
- umap/static/umap/js/modules/autocomplete.js +347 -0
- umap/static/umap/js/modules/browser.js +6 -6
- umap/static/umap/js/modules/caption.js +5 -4
- umap/static/umap/js/modules/global.js +36 -12
- umap/static/umap/js/modules/help.js +255 -0
- umap/static/umap/js/modules/importer.js +308 -0
- umap/static/umap/js/modules/importers/communesfr.js +44 -0
- umap/static/umap/js/modules/importers/datasets.js +42 -0
- umap/static/umap/js/modules/importers/geodatamine.js +95 -0
- umap/static/umap/js/modules/importers/overpass.js +84 -0
- umap/static/umap/js/modules/request.js +12 -14
- umap/static/umap/js/modules/rules.js +241 -0
- umap/static/umap/js/modules/schema.js +63 -14
- umap/static/umap/js/modules/sync/engine.js +93 -0
- umap/static/umap/js/modules/sync/updaters.js +109 -0
- umap/static/umap/js/modules/sync/websocket.js +25 -0
- umap/static/umap/js/modules/ui/dialog.js +52 -0
- umap/static/umap/js/modules/{panel.js → ui/panel.js} +25 -14
- umap/static/umap/js/modules/ui/tooltip.js +116 -0
- umap/static/umap/js/modules/utils.js +25 -18
- umap/static/umap/js/umap.controls.js +13 -14
- umap/static/umap/js/umap.core.js +1 -324
- umap/static/umap/js/umap.features.js +77 -29
- umap/static/umap/js/umap.forms.js +9 -13
- umap/static/umap/js/umap.js +254 -215
- umap/static/umap/js/umap.layer.js +152 -74
- umap/static/umap/js/umap.permissions.js +5 -9
- umap/static/umap/js/umap.popup.js +1 -1
- umap/static/umap/js/umap.tableeditor.js +8 -8
- umap/static/umap/locale/am_ET.js +51 -16
- umap/static/umap/locale/am_ET.json +51 -16
- umap/static/umap/locale/ar.js +51 -16
- umap/static/umap/locale/ar.json +51 -16
- umap/static/umap/locale/ast.js +51 -16
- umap/static/umap/locale/ast.json +51 -16
- umap/static/umap/locale/bg.js +51 -16
- umap/static/umap/locale/bg.json +51 -16
- umap/static/umap/locale/br.js +55 -20
- umap/static/umap/locale/br.json +55 -20
- umap/static/umap/locale/ca.js +51 -16
- umap/static/umap/locale/ca.json +51 -16
- umap/static/umap/locale/cs_CZ.js +93 -58
- umap/static/umap/locale/cs_CZ.json +93 -58
- umap/static/umap/locale/da.js +51 -16
- umap/static/umap/locale/da.json +51 -16
- umap/static/umap/locale/de.js +56 -21
- umap/static/umap/locale/de.json +56 -21
- umap/static/umap/locale/el.js +51 -16
- umap/static/umap/locale/el.json +51 -16
- umap/static/umap/locale/en.js +52 -16
- umap/static/umap/locale/en.json +52 -16
- umap/static/umap/locale/en_US.json +51 -16
- umap/static/umap/locale/es.js +51 -16
- umap/static/umap/locale/es.json +51 -16
- umap/static/umap/locale/et.js +51 -16
- umap/static/umap/locale/et.json +51 -16
- umap/static/umap/locale/eu.js +51 -16
- umap/static/umap/locale/eu.json +51 -16
- umap/static/umap/locale/fa_IR.js +51 -16
- umap/static/umap/locale/fa_IR.json +51 -16
- umap/static/umap/locale/fi.js +51 -16
- umap/static/umap/locale/fi.json +51 -16
- umap/static/umap/locale/fr.js +61 -25
- umap/static/umap/locale/fr.json +61 -25
- umap/static/umap/locale/gl.js +51 -16
- umap/static/umap/locale/gl.json +51 -16
- umap/static/umap/locale/he.js +51 -16
- umap/static/umap/locale/he.json +51 -16
- umap/static/umap/locale/hr.js +51 -16
- umap/static/umap/locale/hr.json +51 -16
- umap/static/umap/locale/hu.js +51 -16
- umap/static/umap/locale/hu.json +51 -16
- umap/static/umap/locale/id.js +51 -16
- umap/static/umap/locale/id.json +51 -16
- umap/static/umap/locale/is.js +51 -16
- umap/static/umap/locale/is.json +51 -16
- umap/static/umap/locale/it.js +51 -16
- umap/static/umap/locale/it.json +51 -16
- umap/static/umap/locale/ja.js +51 -16
- umap/static/umap/locale/ja.json +51 -16
- umap/static/umap/locale/ko.js +51 -16
- umap/static/umap/locale/ko.json +51 -16
- umap/static/umap/locale/lt.js +51 -16
- umap/static/umap/locale/lt.json +51 -16
- umap/static/umap/locale/ms.js +51 -16
- umap/static/umap/locale/ms.json +51 -16
- umap/static/umap/locale/nl.js +51 -16
- umap/static/umap/locale/nl.json +51 -16
- umap/static/umap/locale/no.js +51 -16
- umap/static/umap/locale/no.json +51 -16
- umap/static/umap/locale/pl.js +93 -58
- umap/static/umap/locale/pl.json +93 -58
- umap/static/umap/locale/pl_PL.json +51 -16
- umap/static/umap/locale/pt.js +215 -180
- umap/static/umap/locale/pt.json +215 -180
- umap/static/umap/locale/pt_BR.js +51 -16
- umap/static/umap/locale/pt_BR.json +51 -16
- umap/static/umap/locale/pt_PT.js +51 -16
- umap/static/umap/locale/pt_PT.json +51 -16
- umap/static/umap/locale/ro.js +51 -16
- umap/static/umap/locale/ro.json +51 -16
- umap/static/umap/locale/ru.js +51 -16
- umap/static/umap/locale/ru.json +51 -16
- umap/static/umap/locale/si.js +51 -16
- umap/static/umap/locale/si.json +51 -16
- umap/static/umap/locale/sk_SK.js +51 -16
- umap/static/umap/locale/sk_SK.json +51 -16
- umap/static/umap/locale/sl.js +51 -16
- umap/static/umap/locale/sl.json +51 -16
- umap/static/umap/locale/sr.js +51 -16
- umap/static/umap/locale/sr.json +51 -16
- umap/static/umap/locale/sv.js +51 -16
- umap/static/umap/locale/sv.json +51 -16
- umap/static/umap/locale/th_TH.js +51 -16
- umap/static/umap/locale/th_TH.json +51 -16
- umap/static/umap/locale/tr.js +51 -16
- umap/static/umap/locale/tr.json +51 -16
- umap/static/umap/locale/uk_UA.js +51 -16
- umap/static/umap/locale/uk_UA.json +51 -16
- umap/static/umap/locale/vi.js +51 -16
- umap/static/umap/locale/vi.json +51 -16
- umap/static/umap/locale/vi_VN.json +51 -16
- umap/static/umap/locale/zh.js +51 -16
- umap/static/umap/locale/zh.json +51 -16
- umap/static/umap/locale/zh_CN.json +51 -16
- umap/static/umap/locale/zh_TW.Big5.json +51 -16
- umap/static/umap/locale/zh_TW.js +51 -16
- umap/static/umap/locale/zh_TW.json +51 -16
- umap/static/umap/map.css +40 -53
- umap/static/umap/unittests/sync.js +105 -0
- umap/static/umap/unittests/utils.js +78 -36
- umap/static/umap/vars.css +19 -1
- umap/static/umap/vendors/formbuilder/Leaflet.FormBuilder.js +2 -2
- umap/templates/umap/components/alerts/alert.html +89 -0
- umap/templates/umap/content.html +4 -3
- umap/templates/umap/css.html +4 -0
- umap/templates/umap/home.html +3 -0
- umap/templates/umap/js.html +0 -3
- umap/templates/umap/map_init.html +2 -8
- umap/templates/umap/messages.html +9 -11
- umap/templates/umap/search.html +3 -0
- umap/tests/base.py +2 -0
- umap/tests/integration/conftest.py +30 -0
- umap/tests/integration/test_anonymous_owned_map.py +8 -13
- umap/tests/integration/test_browser.py +77 -4
- umap/tests/integration/test_conditional_rules.py +201 -0
- umap/tests/integration/test_dashboard.py +1 -1
- umap/tests/integration/test_datalayer.py +2 -3
- umap/tests/integration/test_edit_datalayer.py +4 -4
- umap/tests/integration/test_edit_map.py +1 -1
- umap/tests/integration/test_facets_browser.py +3 -3
- umap/tests/integration/test_import.py +185 -49
- umap/tests/integration/test_map.py +31 -2
- umap/tests/integration/{test_collaborative_editing.py → test_optimistic_merge.py} +7 -7
- umap/tests/integration/test_owned_map.py +1 -1
- umap/tests/integration/test_picto.py +2 -2
- umap/tests/integration/test_statics.py +1 -1
- umap/tests/integration/test_view_marker.py +2 -2
- umap/tests/integration/test_websocket_sync.py +283 -0
- umap/tests/settings.py +5 -0
- umap/tests/test_datalayer_views.py +0 -1
- umap/tests/test_views.py +53 -0
- umap/urls.py +5 -0
- umap/views.py +40 -11
- umap/websocket_server.py +92 -0
- {umap_project-2.3.1.dist-info → umap_project-2.4.0.dist-info}/METADATA +10 -8
- {umap_project-2.3.1.dist-info → umap_project-2.4.0.dist-info}/RECORD +201 -167
- umap/static/umap/js/umap.autocomplete.js +0 -341
- umap/static/umap/js/umap.importer.js +0 -187
- umap/static/umap/js/umap.ui.js +0 -190
- {umap_project-2.3.1.dist-info → umap_project-2.4.0.dist-info}/WHEEL +0 -0
- {umap_project-2.3.1.dist-info → umap_project-2.4.0.dist-info}/entry_points.txt +0 -0
- {umap_project-2.3.1.dist-info → umap_project-2.4.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { WebSocketTransport } from './websocket.js'
|
|
2
|
+
import { MapUpdater, DataLayerUpdater, FeatureUpdater } from './updaters.js'
|
|
3
|
+
|
|
4
|
+
export class SyncEngine {
|
|
5
|
+
constructor(map) {
|
|
6
|
+
this.updaters = {
|
|
7
|
+
map: new MapUpdater(map),
|
|
8
|
+
feature: new FeatureUpdater(map),
|
|
9
|
+
datalayer: new DataLayerUpdater(map),
|
|
10
|
+
}
|
|
11
|
+
this.transport = undefined
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async authenticate(tokenURI, webSocketURI, server) {
|
|
15
|
+
const [response, _, error] = await server.get(tokenURI)
|
|
16
|
+
if (!error) {
|
|
17
|
+
this.start(webSocketURI, response.token)
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
start(webSocketURI, authToken) {
|
|
22
|
+
this.transport = new WebSocketTransport(webSocketURI, authToken, this)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
stop() {
|
|
26
|
+
if (this.transport) this.transport.close()
|
|
27
|
+
this.transport = undefined
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
_getUpdater(subject, metadata) {
|
|
31
|
+
if (Object.keys(this.updaters).includes(subject)) {
|
|
32
|
+
return this.updaters[subject]
|
|
33
|
+
}
|
|
34
|
+
throw new Error(`Unknown updater ${subject}, ${metadata}`)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// This method is called by the transport layer on new messages
|
|
38
|
+
receive({ kind, ...payload }) {
|
|
39
|
+
if (kind == 'operation') {
|
|
40
|
+
let updater = this._getUpdater(payload.subject, payload.metadata)
|
|
41
|
+
updater.applyMessage(payload)
|
|
42
|
+
} else {
|
|
43
|
+
throw new Error(`Unknown dispatch kind: ${kind}`)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
_send(message) {
|
|
48
|
+
if (this.transport) {
|
|
49
|
+
this.transport.send('operation', message)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
upsert(subject, metadata, value) {
|
|
54
|
+
this._send({ verb: 'upsert', subject, metadata, value })
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
update(subject, metadata, key, value) {
|
|
58
|
+
this._send({ verb: 'update', subject, metadata, key, value })
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
delete(subject, metadata, key) {
|
|
62
|
+
this._send({ verb: 'delete', subject, metadata, key })
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Create a proxy for this sync engine.
|
|
67
|
+
*
|
|
68
|
+
* The proxy will automatically call `object.getSyncMetadata` and inject the returned
|
|
69
|
+
* `subject` and `metadata`` to the `upsert`, `update` and `delete` calls.
|
|
70
|
+
*
|
|
71
|
+
* The proxy can be used as follows:
|
|
72
|
+
*
|
|
73
|
+
* ```
|
|
74
|
+
* const proxy = sync.proxy(object)
|
|
75
|
+
* proxy.update('key', 'value')
|
|
76
|
+
*```
|
|
77
|
+
*/
|
|
78
|
+
proxy(object) {
|
|
79
|
+
const handler = {
|
|
80
|
+
get(target, prop) {
|
|
81
|
+
// Only proxy these methods
|
|
82
|
+
if (['upsert', 'update', 'delete'].includes(prop)) {
|
|
83
|
+
const { subject, metadata } = object.getSyncMetadata()
|
|
84
|
+
// Reflect.get is calling the original method.
|
|
85
|
+
// .bind is adding the parameters automatically
|
|
86
|
+
return Reflect.get(...arguments).bind(target, subject, metadata)
|
|
87
|
+
}
|
|
88
|
+
return Reflect.get(...arguments)
|
|
89
|
+
},
|
|
90
|
+
}
|
|
91
|
+
return new Proxy(this, handler)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file contains the updaters: classes that are able to convert messages
|
|
3
|
+
* received from another party (or the server) to changes on the map.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
class BaseUpdater {
|
|
7
|
+
constructor(map) {
|
|
8
|
+
this.map = map
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
updateObjectValue(obj, key, value) {
|
|
12
|
+
const parts = key.split('.')
|
|
13
|
+
const lastKey = parts.pop()
|
|
14
|
+
|
|
15
|
+
// Reduce the current list of attributes,
|
|
16
|
+
// to find the object to set the property onto
|
|
17
|
+
const objectToSet = parts.reduce((currentObj, part) => {
|
|
18
|
+
if (currentObj !== undefined && part in currentObj) return currentObj[part]
|
|
19
|
+
}, obj)
|
|
20
|
+
|
|
21
|
+
// In case the given path doesn't exist, stop here
|
|
22
|
+
if (objectToSet === undefined) return
|
|
23
|
+
|
|
24
|
+
// Set the value (or delete it)
|
|
25
|
+
if (typeof value === 'undefined') {
|
|
26
|
+
delete objectToSet[lastKey]
|
|
27
|
+
} else {
|
|
28
|
+
objectToSet[lastKey] = value
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
getDataLayerFromID(layerId) {
|
|
33
|
+
if (layerId) return this.map.getDataLayerByUmapId(layerId)
|
|
34
|
+
return this.map.defaultEditDataLayer()
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
applyMessage(payload) {
|
|
38
|
+
let { verb } = payload
|
|
39
|
+
return this[verb](payload)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export class MapUpdater extends BaseUpdater {
|
|
44
|
+
update({ key, value }) {
|
|
45
|
+
this.updateObjectValue(this.map, key, value)
|
|
46
|
+
this.map.render([key])
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export class DataLayerUpdater extends BaseUpdater {
|
|
51
|
+
upsert({ value }) {
|
|
52
|
+
// Inserts does not happen (we use multiple updates instead).
|
|
53
|
+
this.map.createDataLayer(value, false)
|
|
54
|
+
this.map.render([])
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
update({ key, metadata, value }) {
|
|
58
|
+
const datalayer = this.getDataLayerFromID(metadata.id)
|
|
59
|
+
this.updateObjectValue(datalayer, key, value)
|
|
60
|
+
datalayer.render([key])
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export class FeatureUpdater extends BaseUpdater {
|
|
65
|
+
getFeatureFromMetadata({ id, layerId }) {
|
|
66
|
+
const datalayer = this.getDataLayerFromID(layerId)
|
|
67
|
+
return datalayer.getFeatureById(id)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Create or update an object at a specific position
|
|
71
|
+
upsert({ metadata, value }) {
|
|
72
|
+
let { id, layerId } = metadata
|
|
73
|
+
const datalayer = this.getDataLayerFromID(layerId)
|
|
74
|
+
let feature = this.getFeatureFromMetadata(metadata, value)
|
|
75
|
+
|
|
76
|
+
feature = datalayer.geoJSONToLeaflet({
|
|
77
|
+
geometry: value.geometry,
|
|
78
|
+
geojson: value,
|
|
79
|
+
id,
|
|
80
|
+
feature,
|
|
81
|
+
})
|
|
82
|
+
datalayer.addLayer(feature)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Update a property of an object
|
|
86
|
+
update({ key, metadata, value }) {
|
|
87
|
+
let feature = this.getFeatureFromMetadata(metadata)
|
|
88
|
+
if (feature === undefined) {
|
|
89
|
+
console.error(`Unable to find feature with id = ${metadata.id}.`)
|
|
90
|
+
}
|
|
91
|
+
switch (key) {
|
|
92
|
+
case 'geometry':
|
|
93
|
+
const datalayer = this.getDataLayerFromID(metadata.layerId)
|
|
94
|
+
datalayer.geoJSONToLeaflet({ geometry: value, id: metadata.id, feature })
|
|
95
|
+
default:
|
|
96
|
+
this.updateObjectValue(feature, key, value)
|
|
97
|
+
feature.datalayer.indexProperties(feature)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
feature.render([key])
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
delete({ metadata }) {
|
|
104
|
+
// XXX Distinguish between properties getting deleted
|
|
105
|
+
// and the wole feature getting deleted
|
|
106
|
+
let feature = this.getFeatureFromMetadata(metadata)
|
|
107
|
+
if (feature) feature.del(false)
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export class WebSocketTransport {
|
|
2
|
+
constructor(webSocketURI, authToken, messagesReceiver) {
|
|
3
|
+
this.websocket = new WebSocket(webSocketURI)
|
|
4
|
+
this.websocket.onopen = () => {
|
|
5
|
+
this.send('join', { token: authToken })
|
|
6
|
+
}
|
|
7
|
+
this.websocket.addEventListener('message', this.onMessage.bind(this))
|
|
8
|
+
this.receiver = messagesReceiver
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
onMessage(wsMessage) {
|
|
12
|
+
this.receiver.receive(JSON.parse(wsMessage.data))
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
send(kind, payload) {
|
|
16
|
+
const message = { ...payload }
|
|
17
|
+
message.kind = kind
|
|
18
|
+
let encoded = JSON.stringify(message)
|
|
19
|
+
this.websocket.send(encoded)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
close() {
|
|
23
|
+
this.websocket.close()
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { DomUtil, DomEvent } from '../../../vendors/leaflet/leaflet-src.esm.js'
|
|
2
|
+
import { translate } from '../i18n.js'
|
|
3
|
+
|
|
4
|
+
export default class Dialog {
|
|
5
|
+
constructor(parent) {
|
|
6
|
+
this.parent = parent
|
|
7
|
+
this.className = 'umap-dialog window'
|
|
8
|
+
this.container = DomUtil.create('dialog', this.className, this.parent)
|
|
9
|
+
DomEvent.disableClickPropagation(this.container)
|
|
10
|
+
DomEvent.on(this.container, 'contextmenu', DomEvent.stopPropagation) // Do not activate our custom context menu.
|
|
11
|
+
DomEvent.on(this.container, 'wheel', DomEvent.stopPropagation)
|
|
12
|
+
DomEvent.on(this.container, 'MozMousePixelScroll', DomEvent.stopPropagation)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
get visible() {
|
|
16
|
+
return this.container.open
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
close() {
|
|
20
|
+
this.container.close()
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
currentZIndex() {
|
|
24
|
+
return Math.max(
|
|
25
|
+
...Array.from(document.querySelectorAll('dialog')).map(
|
|
26
|
+
(el) => window.getComputedStyle(el).getPropertyValue('z-index') || 0
|
|
27
|
+
)
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
open({ className, content, modal } = {}) {
|
|
32
|
+
this.container.innerHTML = ''
|
|
33
|
+
const currentZIndex = this.currentZIndex()
|
|
34
|
+
if (currentZIndex) this.container.style.zIndex = currentZIndex + 1
|
|
35
|
+
if (modal) this.container.showModal()
|
|
36
|
+
else this.container.show()
|
|
37
|
+
if (className) {
|
|
38
|
+
// Reset
|
|
39
|
+
this.container.className = this.className
|
|
40
|
+
this.container.classList.add(...className.split(' '))
|
|
41
|
+
}
|
|
42
|
+
const buttonsContainer = DomUtil.create('ul', 'buttons', this.container)
|
|
43
|
+
const closeButton = DomUtil.createButtonIcon(
|
|
44
|
+
DomUtil.create('li', '', buttonsContainer),
|
|
45
|
+
'icon-close',
|
|
46
|
+
translate('Close')
|
|
47
|
+
)
|
|
48
|
+
DomEvent.on(closeButton, 'click', this.close, this)
|
|
49
|
+
this.container.appendChild(buttonsContainer)
|
|
50
|
+
this.container.appendChild(content)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { DomUtil, DomEvent } from '
|
|
2
|
-
import { translate } from '
|
|
1
|
+
import { DomUtil, DomEvent } from '../../../vendors/leaflet/leaflet-src.esm.js'
|
|
2
|
+
import { translate } from '../i18n.js'
|
|
3
3
|
|
|
4
4
|
export class Panel {
|
|
5
5
|
constructor(map) {
|
|
@@ -20,28 +20,39 @@ export class Panel {
|
|
|
20
20
|
if (!this.mode) this.mode = mode
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
isOpen() {
|
|
24
|
+
return this.container.classList.contains('on')
|
|
25
|
+
}
|
|
26
|
+
|
|
23
27
|
open({ content, className, actions = [] } = {}) {
|
|
24
|
-
this.container.className = `with-transition panel ${this.classname} ${
|
|
28
|
+
this.container.className = `with-transition panel window ${this.classname} ${
|
|
29
|
+
this.mode || ''
|
|
30
|
+
}`
|
|
25
31
|
this.container.innerHTML = ''
|
|
26
|
-
const actionsContainer = DomUtil.create('ul', '
|
|
32
|
+
const actionsContainer = DomUtil.create('ul', 'buttons', this.container)
|
|
27
33
|
const body = DomUtil.create('div', 'body', this.container)
|
|
28
34
|
body.appendChild(content)
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
const closeButton = DomUtil.createButtonIcon(
|
|
36
|
+
DomUtil.create('li', '', actionsContainer),
|
|
37
|
+
'icon-close',
|
|
38
|
+
translate('Close')
|
|
39
|
+
)
|
|
40
|
+
const resizeButton = DomUtil.createButtonIcon(
|
|
41
|
+
DomUtil.create('li', '', actionsContainer),
|
|
42
|
+
'icon-resize',
|
|
43
|
+
translate('Toggle size')
|
|
44
|
+
)
|
|
45
|
+
for (const action of actions) {
|
|
46
|
+
const element = DomUtil.element({ tagName: 'li', parent: actionsContainer })
|
|
47
|
+
element.appendChild(action)
|
|
37
48
|
}
|
|
38
49
|
if (className) DomUtil.addClass(body, className)
|
|
39
50
|
const promise = new Promise((resolve, reject) => {
|
|
40
51
|
DomUtil.addClass(this.container, 'on')
|
|
41
52
|
resolve()
|
|
42
53
|
})
|
|
43
|
-
DomEvent.on(
|
|
44
|
-
DomEvent.on(
|
|
54
|
+
DomEvent.on(closeButton, 'click', this.close, this)
|
|
55
|
+
DomEvent.on(resizeButton, 'click', this.resize, this)
|
|
45
56
|
return promise
|
|
46
57
|
}
|
|
47
58
|
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { DomUtil, DomEvent } from '../../../vendors/leaflet/leaflet-src.esm.js'
|
|
2
|
+
import { translate } from '../i18n.js'
|
|
3
|
+
|
|
4
|
+
export default class Tooltip {
|
|
5
|
+
constructor(parent) {
|
|
6
|
+
this.parent = parent
|
|
7
|
+
this.container = DomUtil.create('div', 'with-transition', this.parent)
|
|
8
|
+
this.container.id = 'umap-tooltip-container'
|
|
9
|
+
DomEvent.disableClickPropagation(this.container)
|
|
10
|
+
DomEvent.on(this.container, 'contextmenu', DomEvent.stopPropagation) // Do not activate our custom context menu.
|
|
11
|
+
DomEvent.on(this.container, 'wheel', DomEvent.stopPropagation)
|
|
12
|
+
DomEvent.on(this.container, 'MozMousePixelScroll', DomEvent.stopPropagation)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
open(opts) {
|
|
16
|
+
function showIt() {
|
|
17
|
+
if (opts.anchor && opts.position === 'top') {
|
|
18
|
+
this.anchorTop(opts.anchor)
|
|
19
|
+
} else if (opts.anchor && opts.position === 'left') {
|
|
20
|
+
this.anchorLeft(opts.anchor)
|
|
21
|
+
} else if (opts.anchor && opts.position === 'bottom') {
|
|
22
|
+
this.anchorBottom(opts.anchor)
|
|
23
|
+
} else {
|
|
24
|
+
this.anchorAbsolute()
|
|
25
|
+
}
|
|
26
|
+
L.DomUtil.addClass(this.parent, 'umap-tooltip')
|
|
27
|
+
this.container.innerHTML = U.Utils.escapeHTML(opts.content)
|
|
28
|
+
}
|
|
29
|
+
this.TOOLTIP_ID = window.setTimeout(L.bind(showIt, this), opts.delay || 0)
|
|
30
|
+
const id = this.TOOLTIP_ID
|
|
31
|
+
const closeIt = () => {
|
|
32
|
+
this.close(id)
|
|
33
|
+
}
|
|
34
|
+
if (opts.anchor) {
|
|
35
|
+
L.DomEvent.once(opts.anchor, 'mouseout', closeIt)
|
|
36
|
+
}
|
|
37
|
+
if (opts.duration !== Infinity) {
|
|
38
|
+
window.setTimeout(closeIt, opts.duration || 3000)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
anchorAbsolute() {
|
|
43
|
+
this.container.className = ''
|
|
44
|
+
const left =
|
|
45
|
+
this.parent.offsetLeft +
|
|
46
|
+
this.parent.clientWidth / 2 -
|
|
47
|
+
this.container.clientWidth / 2,
|
|
48
|
+
top = this.parent.offsetTop + 75
|
|
49
|
+
this.setPosition({ top: top, left: left })
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
anchorTop(el) {
|
|
53
|
+
this.container.className = 'tooltip-top'
|
|
54
|
+
const coords = this.getPosition(el)
|
|
55
|
+
this.setPosition({
|
|
56
|
+
left: coords.left - 10,
|
|
57
|
+
bottom: this.getDocHeight() - coords.top + 11,
|
|
58
|
+
})
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
anchorBottom(el) {
|
|
62
|
+
this.container.className = 'tooltip-bottom'
|
|
63
|
+
const coords = this.getPosition(el)
|
|
64
|
+
this.setPosition({
|
|
65
|
+
left: coords.left,
|
|
66
|
+
top: coords.bottom + 11,
|
|
67
|
+
})
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
anchorLeft(el) {
|
|
71
|
+
this.container.className = 'tooltip-left'
|
|
72
|
+
const coords = this.getPosition(el)
|
|
73
|
+
this.setPosition({
|
|
74
|
+
top: coords.top,
|
|
75
|
+
right: document.documentElement.offsetWidth - coords.left + 11,
|
|
76
|
+
})
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
close(id) {
|
|
80
|
+
// Clear timetout even if a new tooltip has been added
|
|
81
|
+
// in the meantime. Eg. after a mouseout from the anchor.
|
|
82
|
+
window.clearTimeout(id)
|
|
83
|
+
if (id && id !== this.TOOLTIP_ID) return
|
|
84
|
+
this.container.className = ''
|
|
85
|
+
this.container.innerHTML = ''
|
|
86
|
+
this.setPosition({})
|
|
87
|
+
L.DomUtil.removeClass(this.parent, 'umap-tooltip')
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
getPosition(el) {
|
|
91
|
+
return el.getBoundingClientRect()
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
setPosition(coords) {
|
|
95
|
+
if (coords.left) this.container.style.left = `${coords.left}px`
|
|
96
|
+
else this.container.style.left = 'initial'
|
|
97
|
+
if (coords.right) this.container.style.right = `${coords.right}px`
|
|
98
|
+
else this.container.style.right = 'initial'
|
|
99
|
+
if (coords.top) this.container.style.top = `${coords.top}px`
|
|
100
|
+
else this.container.style.top = 'initial'
|
|
101
|
+
if (coords.bottom) this.container.style.bottom = `${coords.bottom}px`
|
|
102
|
+
else this.container.style.bottom = 'initial'
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
getDocHeight() {
|
|
106
|
+
const D = document
|
|
107
|
+
return Math.max(
|
|
108
|
+
D.body.scrollHeight,
|
|
109
|
+
D.documentElement.scrollHeight,
|
|
110
|
+
D.body.offsetHeight,
|
|
111
|
+
D.documentElement.offsetHeight,
|
|
112
|
+
D.body.clientHeight,
|
|
113
|
+
D.documentElement.clientHeight
|
|
114
|
+
)
|
|
115
|
+
}
|
|
116
|
+
}
|
|
@@ -30,7 +30,8 @@ export function checkId(string) {
|
|
|
30
30
|
*
|
|
31
31
|
* Return an array of unique impacts.
|
|
32
32
|
*
|
|
33
|
-
* @param {fields}
|
|
33
|
+
* @param {fields} list[fields]
|
|
34
|
+
* @param object schema object. If ommited, global U.SCHEMA will be used.
|
|
34
35
|
* @returns Array[string]
|
|
35
36
|
*/
|
|
36
37
|
export function getImpactsFromSchema(fields, schema) {
|
|
@@ -84,11 +85,23 @@ export function escapeHTML(s) {
|
|
|
84
85
|
'div',
|
|
85
86
|
'iframe',
|
|
86
87
|
'img',
|
|
88
|
+
'audio',
|
|
89
|
+
'video',
|
|
90
|
+
'source',
|
|
87
91
|
'br',
|
|
88
92
|
'span',
|
|
93
|
+
'dt',
|
|
94
|
+
'dd',
|
|
89
95
|
],
|
|
90
|
-
ADD_ATTR: [
|
|
91
|
-
|
|
96
|
+
ADD_ATTR: [
|
|
97
|
+
'target',
|
|
98
|
+
'allow',
|
|
99
|
+
'allowfullscreen',
|
|
100
|
+
'frameborder',
|
|
101
|
+
'scrolling',
|
|
102
|
+
'controls',
|
|
103
|
+
],
|
|
104
|
+
ALLOWED_ATTR: ['href', 'src', 'width', 'height', 'style', 'dir', 'title', 'type'],
|
|
92
105
|
// Added: `geo:` URL scheme as defined in RFC5870:
|
|
93
106
|
// https://www.rfc-editor.org/rfc/rfc5870.html
|
|
94
107
|
// The base RegExp comes from:
|
|
@@ -102,27 +115,24 @@ export function escapeHTML(s) {
|
|
|
102
115
|
export function toHTML(r, options) {
|
|
103
116
|
if (!r) return ''
|
|
104
117
|
const target = (options && options.target) || 'blank'
|
|
105
|
-
let ii
|
|
106
118
|
|
|
107
|
-
//
|
|
108
|
-
|
|
119
|
+
// unordered lists
|
|
120
|
+
r = r.replace(/^\*\* (.*)/gm, '<ul><ul><li>$1</li></ul></ul>')
|
|
121
|
+
r = r.replace(/^\* (.*)/gm, '<ul><li>$1</li></ul>')
|
|
122
|
+
for (let ii = 0; ii < 3; ii++) {
|
|
123
|
+
r = r.replace(new RegExp(`</ul>(\r\n|\r|\n)<ul>`, 'g'), '')
|
|
124
|
+
}
|
|
109
125
|
|
|
110
126
|
// headings and hr
|
|
111
|
-
r = r.replace(/^### (.*)
|
|
112
|
-
r = r.replace(/^## (.*)
|
|
113
|
-
r = r.replace(/^# (.*)
|
|
127
|
+
r = r.replace(/^### (.*)(\r\n|\r|\n)?/gm, '<h6>$1</h6>')
|
|
128
|
+
r = r.replace(/^## (.*)(\r\n|\r|\n)?/gm, '<h5>$1</h5>')
|
|
129
|
+
r = r.replace(/^# (.*)(\r\n|\r|\n)?/gm, '<h4>$1</h4>')
|
|
114
130
|
r = r.replace(/^---/gm, '<hr>')
|
|
115
131
|
|
|
116
132
|
// bold, italics
|
|
117
133
|
r = r.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
|
|
118
134
|
r = r.replace(/\*(.*?)\*/g, '<em>$1</em>')
|
|
119
135
|
|
|
120
|
-
// unordered lists
|
|
121
|
-
r = r.replace(/^\*\* (.*)/gm, '<ul><ul><li>$1</li></ul></ul>')
|
|
122
|
-
r = r.replace(/^\* (.*)/gm, '<ul><li>$1</li></ul>')
|
|
123
|
-
for (ii = 0; ii < 3; ii++)
|
|
124
|
-
r = r.replace(new RegExp(`</ul>${newline}<ul>`, 'g'), newline)
|
|
125
|
-
|
|
126
136
|
// links
|
|
127
137
|
r = r.replace(/(\[\[http)/g, '[[h_t_t_p') // Escape for avoiding clash between [[http://xxx]] and http://xxx
|
|
128
138
|
r = r.replace(/({{http)/g, '{{h_t_t_p')
|
|
@@ -163,9 +173,6 @@ export function toHTML(r, options) {
|
|
|
163
173
|
//Unescape http
|
|
164
174
|
r = r.replace(/(h_t_t_p)/g, 'http')
|
|
165
175
|
|
|
166
|
-
// Preserver line breaks
|
|
167
|
-
if (newline) r = r.replace(new RegExp(`${newline}(?=[^]+)`, 'g'), `<br>${newline}`)
|
|
168
|
-
|
|
169
176
|
r = escapeHTML(r)
|
|
170
177
|
|
|
171
178
|
return r
|
|
@@ -84,7 +84,7 @@ U.UpdateExtentAction = U.BaseAction.extend({
|
|
|
84
84
|
},
|
|
85
85
|
|
|
86
86
|
addHooks: function () {
|
|
87
|
-
this.map.
|
|
87
|
+
this.map.setCenterAndZoom()
|
|
88
88
|
},
|
|
89
89
|
})
|
|
90
90
|
|
|
@@ -394,7 +394,7 @@ U.EditControl = L.Control.extend({
|
|
|
394
394
|
enableEditing,
|
|
395
395
|
'mouseover',
|
|
396
396
|
function () {
|
|
397
|
-
map.
|
|
397
|
+
map.tooltip.open({
|
|
398
398
|
content: map.help.displayLabel('TOGGLE_EDIT'),
|
|
399
399
|
anchor: enableEditing,
|
|
400
400
|
position: 'bottom',
|
|
@@ -510,7 +510,7 @@ U.DataLayersControl = L.Control.Button.extend({
|
|
|
510
510
|
options: {
|
|
511
511
|
position: 'topleft',
|
|
512
512
|
className: 'umap-control-browse',
|
|
513
|
-
title: L._('
|
|
513
|
+
title: L._('Open browser'),
|
|
514
514
|
},
|
|
515
515
|
|
|
516
516
|
afterAdd: function (container) {
|
|
@@ -693,7 +693,7 @@ const ControlsMixin = {
|
|
|
693
693
|
nameButton,
|
|
694
694
|
'mouseover',
|
|
695
695
|
function () {
|
|
696
|
-
this.
|
|
696
|
+
this.tooltip.open({
|
|
697
697
|
content: L._('Edit the title of the map'),
|
|
698
698
|
anchor: nameButton,
|
|
699
699
|
position: 'bottom',
|
|
@@ -714,7 +714,7 @@ const ControlsMixin = {
|
|
|
714
714
|
shareStatusButton,
|
|
715
715
|
'mouseover',
|
|
716
716
|
function () {
|
|
717
|
-
this.
|
|
717
|
+
this.tooltip.open({
|
|
718
718
|
content: L._('Update who can see and edit the map'),
|
|
719
719
|
anchor: shareStatusButton,
|
|
720
720
|
position: 'bottom',
|
|
@@ -763,7 +763,7 @@ const ControlsMixin = {
|
|
|
763
763
|
controlEditCancel,
|
|
764
764
|
'mouseover',
|
|
765
765
|
function () {
|
|
766
|
-
this.
|
|
766
|
+
this.tooltip.open({
|
|
767
767
|
content: this.help.displayLabel('CANCEL'),
|
|
768
768
|
anchor: controlEditCancel,
|
|
769
769
|
position: 'bottom',
|
|
@@ -784,7 +784,7 @@ const ControlsMixin = {
|
|
|
784
784
|
controlEditDisable,
|
|
785
785
|
'mouseover',
|
|
786
786
|
function () {
|
|
787
|
-
this.
|
|
787
|
+
this.tooltip.open({
|
|
788
788
|
content: this.help.displayLabel('PREVIEW'),
|
|
789
789
|
anchor: controlEditDisable,
|
|
790
790
|
position: 'bottom',
|
|
@@ -805,7 +805,7 @@ const ControlsMixin = {
|
|
|
805
805
|
controlEditSave,
|
|
806
806
|
'mouseover',
|
|
807
807
|
function () {
|
|
808
|
-
this.
|
|
808
|
+
this.tooltip.open({
|
|
809
809
|
content: this.help.displayLabel('SAVE'),
|
|
810
810
|
anchor: controlEditSave,
|
|
811
811
|
position: 'bottom',
|
|
@@ -992,7 +992,7 @@ U.AttributionControl = L.Control.Attribution.extend({
|
|
|
992
992
|
})
|
|
993
993
|
}
|
|
994
994
|
if (captionMenus) {
|
|
995
|
-
const link = L.DomUtil.add('a', '', container, ` — ${L._('
|
|
995
|
+
const link = L.DomUtil.add('a', '', container, ` — ${L._('Open caption')}`)
|
|
996
996
|
L.DomEvent.on(link, 'click', L.DomEvent.stop)
|
|
997
997
|
.on(link, 'click', this._map.openCaption, this._map)
|
|
998
998
|
.on(link, 'dblclick', L.DomEvent.stop)
|
|
@@ -1048,7 +1048,6 @@ U.Locate = L.Control.Locate.extend({
|
|
|
1048
1048
|
if (!this._container || !this._container.parentNode) return
|
|
1049
1049
|
return L.Control.Locate.prototype.remove.call(this)
|
|
1050
1050
|
},
|
|
1051
|
-
|
|
1052
1051
|
})
|
|
1053
1052
|
|
|
1054
1053
|
U.Search = L.PhotonSearch.extend({
|
|
@@ -1087,7 +1086,7 @@ U.Search = L.PhotonSearch.extend({
|
|
|
1087
1086
|
if (latlng.isValid()) {
|
|
1088
1087
|
this.reverse.doReverse(latlng)
|
|
1089
1088
|
} else {
|
|
1090
|
-
|
|
1089
|
+
U.Alert.error(L._('Invalid latitude or longitude'))
|
|
1091
1090
|
}
|
|
1092
1091
|
return
|
|
1093
1092
|
}
|
|
@@ -1250,7 +1249,7 @@ U.Editable = L.Editable.extend({
|
|
|
1250
1249
|
L.Editable.prototype.initialize.call(this, map, options)
|
|
1251
1250
|
this.on('editable:drawing:click editable:drawing:move', this.drawingTooltip)
|
|
1252
1251
|
this.on('editable:drawing:end', (e) => {
|
|
1253
|
-
this.
|
|
1252
|
+
this.map.tooltip.close()
|
|
1254
1253
|
// Leaflet.Editable will delete the drawn shape if invalid
|
|
1255
1254
|
// (eg. line has only one drawn point)
|
|
1256
1255
|
// So let's check if the layer has no more shape
|
|
@@ -1314,7 +1313,7 @@ U.Editable = L.Editable.extend({
|
|
|
1314
1313
|
|
|
1315
1314
|
drawingTooltip: function (e) {
|
|
1316
1315
|
if (e.layer instanceof L.Marker && e.type == 'editable:drawing:start') {
|
|
1317
|
-
this.map.
|
|
1316
|
+
this.map.tooltip.open({ content: L._('Click to add a marker') })
|
|
1318
1317
|
}
|
|
1319
1318
|
if (!(e.layer instanceof L.Polyline)) {
|
|
1320
1319
|
// only continue with Polylines and Polygons
|
|
@@ -1357,7 +1356,7 @@ U.Editable = L.Editable.extend({
|
|
|
1357
1356
|
}
|
|
1358
1357
|
}
|
|
1359
1358
|
if (content) {
|
|
1360
|
-
this.map.
|
|
1359
|
+
this.map.tooltip.open({ content: content })
|
|
1361
1360
|
}
|
|
1362
1361
|
},
|
|
1363
1362
|
|