umap-project 2.3.0__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/locale/pl/LC_MESSAGES/django.mo +0 -0
- umap/locale/pl/LC_MESSAGES/django.po +83 -78
- umap/locale/pt/LC_MESSAGES/django.mo +0 -0
- umap/locale/pt/LC_MESSAGES/django.po +129 -123
- 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 +68 -186
- 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 +14 -21
- umap/static/umap/js/modules/caption.js +119 -0
- umap/static/umap/js/modules/global.js +37 -11
- 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} +37 -20
- 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 +37 -112
- umap/static/umap/js/umap.core.js +1 -327
- umap/static/umap/js/umap.features.js +77 -29
- umap/static/umap/js/umap.forms.js +17 -19
- umap/static/umap/js/umap.js +265 -228
- umap/static/umap/js/umap.layer.js +154 -76
- umap/static/umap/js/umap.permissions.js +5 -9
- umap/static/umap/js/umap.popup.js +2 -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/dompurify/purify.es.js +50 -15
- umap/static/umap/vendors/dompurify/purify.es.mjs.map +1 -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 +3 -0
- umap/tests/integration/conftest.py +30 -0
- umap/tests/integration/test_anonymous_owned_map.py +8 -13
- umap/tests/integration/test_browser.py +81 -6
- umap/tests/integration/test_caption.py +27 -0
- 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 +32 -3
- umap/tests/integration/test_edit_map.py +1 -1
- umap/tests/integration/test_facets_browser.py +7 -4
- umap/tests/integration/test_import.py +185 -49
- umap/tests/integration/test_map.py +31 -17
- 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 +19 -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.0.dist-info → umap_project-2.4.0.dist-info}/METADATA +13 -11
- {umap_project-2.3.0.dist-info → umap_project-2.4.0.dist-info}/RECORD +208 -172
- 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.0.dist-info → umap_project-2.4.0.dist-info}/WHEEL +0 -0
- {umap_project-2.3.0.dist-info → umap_project-2.4.0.dist-info}/entry_points.txt +0 -0
- {umap_project-2.3.0.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,12 +1,14 @@
|
|
|
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) {
|
|
6
6
|
this.parent = map._controlContainer
|
|
7
7
|
this.map = map
|
|
8
8
|
this.container = DomUtil.create('div', '', this.parent)
|
|
9
|
-
|
|
9
|
+
// This will be set once according to the panel configurated at load
|
|
10
|
+
// or by using panels as popups
|
|
11
|
+
this.mode = null
|
|
10
12
|
this.classname = 'left'
|
|
11
13
|
DomEvent.disableClickPropagation(this.container)
|
|
12
14
|
DomEvent.on(this.container, 'contextmenu', DomEvent.stopPropagation) // Do not activate our custom context menu.
|
|
@@ -14,40 +16,55 @@ export class Panel {
|
|
|
14
16
|
DomEvent.on(this.container, 'MozMousePixelScroll', DomEvent.stopPropagation)
|
|
15
17
|
}
|
|
16
18
|
|
|
19
|
+
setDefaultMode(mode) {
|
|
20
|
+
if (!this.mode) this.mode = mode
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
isOpen() {
|
|
24
|
+
return this.container.classList.contains('on')
|
|
25
|
+
}
|
|
26
|
+
|
|
17
27
|
open({ content, className, actions = [] } = {}) {
|
|
18
|
-
this.container.className = `with-transition panel ${this.classname} ${
|
|
28
|
+
this.container.className = `with-transition panel window ${this.classname} ${
|
|
29
|
+
this.mode || ''
|
|
30
|
+
}`
|
|
19
31
|
this.container.innerHTML = ''
|
|
20
|
-
const actionsContainer = DomUtil.create('ul', '
|
|
32
|
+
const actionsContainer = DomUtil.create('ul', 'buttons', this.container)
|
|
21
33
|
const body = DomUtil.create('div', 'body', this.container)
|
|
22
34
|
body.appendChild(content)
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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)
|
|
31
48
|
}
|
|
32
49
|
if (className) DomUtil.addClass(body, className)
|
|
33
50
|
const promise = new Promise((resolve, reject) => {
|
|
34
51
|
DomUtil.addClass(this.container, 'on')
|
|
35
52
|
resolve()
|
|
36
53
|
})
|
|
37
|
-
DomEvent.on(
|
|
38
|
-
DomEvent.on(
|
|
54
|
+
DomEvent.on(closeButton, 'click', this.close, this)
|
|
55
|
+
DomEvent.on(resizeButton, 'click', this.resize, this)
|
|
39
56
|
return promise
|
|
40
57
|
}
|
|
41
58
|
|
|
42
59
|
resize() {
|
|
43
|
-
if (this.mode === '
|
|
44
|
-
this.mode = 'condensed'
|
|
45
|
-
this.container.classList.remove('expanded')
|
|
46
|
-
this.container.classList.add('condensed')
|
|
47
|
-
} else {
|
|
60
|
+
if (this.mode === 'condensed') {
|
|
48
61
|
this.mode = 'expanded'
|
|
49
62
|
this.container.classList.remove('condensed')
|
|
50
63
|
this.container.classList.add('expanded')
|
|
64
|
+
} else {
|
|
65
|
+
this.mode = 'condensed'
|
|
66
|
+
this.container.classList.remove('expanded')
|
|
67
|
+
this.container.classList.add('condensed')
|
|
51
68
|
}
|
|
52
69
|
}
|
|
53
70
|
|
|
@@ -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
|