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,95 @@
|
|
|
1
|
+
import { DomUtil, DomEvent } from '../../../vendors/leaflet/leaflet-src.esm.js'
|
|
2
|
+
import { BaseAjax, SingleMixin } from '../autocomplete.js'
|
|
3
|
+
import { translate } from '../i18n.js'
|
|
4
|
+
import * as Utils from '../utils.js'
|
|
5
|
+
import { uMapAlert as Alert } from '../../components/alerts/alert.js'
|
|
6
|
+
|
|
7
|
+
const BOUNDARY_TYPES = {
|
|
8
|
+
admin_6: 'département',
|
|
9
|
+
admin_7: 'pays (loi Voynet)',
|
|
10
|
+
admin_8: 'commune',
|
|
11
|
+
admin_9: 'quartier, hameau, arrondissement',
|
|
12
|
+
political: 'canton',
|
|
13
|
+
local_authority: 'EPCI',
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const TEMPLATE = `
|
|
17
|
+
<h3>GeoDataMine</h3>
|
|
18
|
+
<p>${translate('GeoDataMine: thematic data from OpenStreetMap')}.</p>
|
|
19
|
+
<select name="theme">
|
|
20
|
+
<option value="">${translate('Choose a theme')}</option>
|
|
21
|
+
</select>
|
|
22
|
+
<label>
|
|
23
|
+
<input type="checkbox" name="aspoint" />
|
|
24
|
+
${translate('Symplify all geometries to points')}
|
|
25
|
+
</label>
|
|
26
|
+
<label id="boundary">
|
|
27
|
+
</label>
|
|
28
|
+
<button class="button">${translate('Choose this data')}</button>
|
|
29
|
+
`
|
|
30
|
+
|
|
31
|
+
class Autocomplete extends SingleMixin(BaseAjax) {
|
|
32
|
+
createResult(item) {
|
|
33
|
+
return super.createResult({
|
|
34
|
+
value: item.id,
|
|
35
|
+
label: `${item.name} (${BOUNDARY_TYPES[item.type]} — ${item.ref})`,
|
|
36
|
+
})
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export class Importer {
|
|
41
|
+
constructor(map, options = {}) {
|
|
42
|
+
this.map = map
|
|
43
|
+
this.name = options.name || 'GeoDataMine'
|
|
44
|
+
this.baseUrl = options?.url || 'https://geodatamine.fr'
|
|
45
|
+
this.id = 'geodatamine'
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async open(importer) {
|
|
49
|
+
let boundary = null
|
|
50
|
+
let boundaryName = null
|
|
51
|
+
const container = DomUtil.create('div')
|
|
52
|
+
container.innerHTML = TEMPLATE
|
|
53
|
+
const response = await importer.map.request.get(`${this.baseUrl}/themes`)
|
|
54
|
+
const select = container.querySelector('select')
|
|
55
|
+
if (response && response.ok) {
|
|
56
|
+
const { themes } = await response.json()
|
|
57
|
+
themes.sort((a, b) => Utils.naturalSort(a['name:fr'], b ['name:fr']))
|
|
58
|
+
for (const theme of themes) {
|
|
59
|
+
DomUtil.element({
|
|
60
|
+
tagName: 'option',
|
|
61
|
+
value: theme.id,
|
|
62
|
+
textContent: theme['name:fr'],
|
|
63
|
+
parent: select,
|
|
64
|
+
})
|
|
65
|
+
}
|
|
66
|
+
} else {
|
|
67
|
+
console.error(response)
|
|
68
|
+
}
|
|
69
|
+
const asPoint = container.querySelector('[name=aspoint]')
|
|
70
|
+
this.autocomplete = new Autocomplete(container.querySelector('#boundary'), {
|
|
71
|
+
placeholder: translate('Search admin boundary'),
|
|
72
|
+
url: `${this.baseUrl}/boundaries/search?text={q}`,
|
|
73
|
+
on_select: (choice) => {
|
|
74
|
+
boundary = choice.item.value
|
|
75
|
+
boundaryName = choice.item.label
|
|
76
|
+
},
|
|
77
|
+
})
|
|
78
|
+
const confirm = () => {
|
|
79
|
+
if (!boundary || !select.value) {
|
|
80
|
+
Alert.error(translate('Please choose a theme and a boundary first.'))
|
|
81
|
+
return
|
|
82
|
+
}
|
|
83
|
+
importer.url = `${this.baseUrl}/data/${select.value}/${boundary}?format=geojson&aspoint=${asPoint.checked}`
|
|
84
|
+
importer.format = 'geojson'
|
|
85
|
+
importer.layerName = `${boundaryName} — ${select.options[select.selectedIndex].textContent}`
|
|
86
|
+
importer.dialog.close()
|
|
87
|
+
}
|
|
88
|
+
DomEvent.on(container.querySelector('button'), 'click', confirm)
|
|
89
|
+
|
|
90
|
+
importer.dialog.open({
|
|
91
|
+
content: container,
|
|
92
|
+
className: `${this.id} importer dark`,
|
|
93
|
+
})
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { DomUtil } from '../../../vendors/leaflet/leaflet-src.esm.js'
|
|
2
|
+
import { BaseAjax, SingleMixin } from '../autocomplete.js'
|
|
3
|
+
import { translate } from '../i18n.js'
|
|
4
|
+
import { uMapAlert as Alert } from '../../components/alerts/alert.js'
|
|
5
|
+
|
|
6
|
+
const TEMPLATE = `
|
|
7
|
+
<h3>Overpass</h3>
|
|
8
|
+
<label>
|
|
9
|
+
<span data-help="overpassImporter">${translate('Expression')}</span>
|
|
10
|
+
<input type="text" placeholder="amenity=drinking_water" name="tags" />
|
|
11
|
+
</label>
|
|
12
|
+
<label>
|
|
13
|
+
${translate('Geometry mode')}
|
|
14
|
+
<select name="out-mode">
|
|
15
|
+
<option value="geom" selected>${translate('Default')}</option>
|
|
16
|
+
<option value="center">${translate('Only geometry centers')}</option>
|
|
17
|
+
</select>
|
|
18
|
+
</label>
|
|
19
|
+
<label id="area"><span>${translate('Search area')}</span></label>
|
|
20
|
+
`
|
|
21
|
+
|
|
22
|
+
class Autocomplete extends SingleMixin(BaseAjax) {
|
|
23
|
+
handleResults(data) {
|
|
24
|
+
return super.handleResults(data.features)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
createResult(item) {
|
|
28
|
+
return super.createResult({
|
|
29
|
+
// Overpass convention to get their id from an osm one.
|
|
30
|
+
value: item.properties.osm_id + 3600000000,
|
|
31
|
+
label: `${item.properties.name}`,
|
|
32
|
+
})
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export class Importer {
|
|
37
|
+
constructor(map, options) {
|
|
38
|
+
this.map = map
|
|
39
|
+
this.name = options.name || 'Overpass'
|
|
40
|
+
this.baseUrl = options?.url || 'https://overpass-api.de/api/interpreter'
|
|
41
|
+
this.id = 'overpass'
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async open(importer) {
|
|
45
|
+
let boundary = null
|
|
46
|
+
let boundaryName = null
|
|
47
|
+
const container = DomUtil.create('div')
|
|
48
|
+
container.innerHTML = TEMPLATE
|
|
49
|
+
this.autocomplete = new Autocomplete(container.querySelector('#area'), {
|
|
50
|
+
url: 'https://photon.komoot.io/api?q={q}&osm_tag=place',
|
|
51
|
+
placeholder: translate(
|
|
52
|
+
'Type area name, or let empty to load data in current map view'
|
|
53
|
+
),
|
|
54
|
+
on_select: (choice) => {
|
|
55
|
+
boundary = choice.item.value
|
|
56
|
+
boundaryName = choice.item.label
|
|
57
|
+
},
|
|
58
|
+
})
|
|
59
|
+
this.map.help.parse(container)
|
|
60
|
+
|
|
61
|
+
const confirm = () => {
|
|
62
|
+
let tags = container.querySelector('[name=tags]').value
|
|
63
|
+
if (!tags) {
|
|
64
|
+
Alert.error(translate('Please define an expression for the query first'))
|
|
65
|
+
return
|
|
66
|
+
}
|
|
67
|
+
const outMode = container.querySelector('[name=out-mode]').value
|
|
68
|
+
if (!tags.startsWith('[')) tags = `[${tags}]`
|
|
69
|
+
let area = '{south},{west},{north},{east}'
|
|
70
|
+
if (boundary) area = `area:${boundary}`
|
|
71
|
+
let query = `[out:json];nwr${tags}(${area});out ${outMode};`
|
|
72
|
+
importer.url = `${this.baseUrl}?data=${query}`
|
|
73
|
+
if (boundary) importer.layerName = boundaryName
|
|
74
|
+
importer.format = 'osm'
|
|
75
|
+
importer.dialog.close()
|
|
76
|
+
}
|
|
77
|
+
L.DomUtil.createButton('', container, translate('Choose this data'), confirm)
|
|
78
|
+
|
|
79
|
+
importer.dialog.open({
|
|
80
|
+
content: container,
|
|
81
|
+
className: `${this.id} importer dark`,
|
|
82
|
+
})
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
1
|
+
import { translate } from './i18n.js'
|
|
2
|
+
import { uMapAlert as Alert } from '../components/alerts/alert.js'
|
|
3
3
|
|
|
4
4
|
export class RequestError extends Error {}
|
|
5
5
|
|
|
@@ -47,14 +47,13 @@ class BaseRequest {
|
|
|
47
47
|
// In case of error, an alert is sent, but non 20X status are not handled
|
|
48
48
|
// The consumer must check the response status by hand
|
|
49
49
|
export class Request extends BaseRequest {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
this.ui = ui
|
|
50
|
+
fire(name, params) {
|
|
51
|
+
document.body.dispatchEvent(new CustomEvent(name, params))
|
|
53
52
|
}
|
|
54
53
|
|
|
55
54
|
async _fetch(method, uri, headers, data) {
|
|
56
55
|
const id = Math.random()
|
|
57
|
-
this.
|
|
56
|
+
this.fire('dataloading', { id: id })
|
|
58
57
|
try {
|
|
59
58
|
const response = await BaseRequest.prototype._fetch.call(
|
|
60
59
|
this,
|
|
@@ -68,7 +67,7 @@ export class Request extends BaseRequest {
|
|
|
68
67
|
if (error instanceof NOKError) return this._onNOK(error)
|
|
69
68
|
return this._onError(error)
|
|
70
69
|
} finally {
|
|
71
|
-
this.
|
|
70
|
+
this.fire('dataload', { id: id })
|
|
72
71
|
}
|
|
73
72
|
}
|
|
74
73
|
|
|
@@ -81,7 +80,7 @@ export class Request extends BaseRequest {
|
|
|
81
80
|
}
|
|
82
81
|
|
|
83
82
|
_onError(error) {
|
|
84
|
-
|
|
83
|
+
Alert.error(translate('Problem in the response'))
|
|
85
84
|
}
|
|
86
85
|
|
|
87
86
|
_onNOK(error) {
|
|
@@ -127,9 +126,9 @@ export class ServerRequest extends Request {
|
|
|
127
126
|
try {
|
|
128
127
|
const data = await response.json()
|
|
129
128
|
if (data.info) {
|
|
130
|
-
|
|
129
|
+
Alert.info(data.info)
|
|
131
130
|
} else if (data.error) {
|
|
132
|
-
|
|
131
|
+
Alert.error(data.error)
|
|
133
132
|
return this._onError(new Error(data.error))
|
|
134
133
|
}
|
|
135
134
|
return [data, response, null]
|
|
@@ -144,10 +143,9 @@ export class ServerRequest extends Request {
|
|
|
144
143
|
|
|
145
144
|
_onNOK(error) {
|
|
146
145
|
if (error.status === 403) {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
})
|
|
146
|
+
Alert.error(error.message || translate('Action not allowed :('))
|
|
147
|
+
} else {
|
|
148
|
+
super._onError(error)
|
|
151
149
|
}
|
|
152
150
|
return [{}, error.response, error]
|
|
153
151
|
}
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { DomUtil, DomEvent, stamp } from '../../vendors/leaflet/leaflet-src.esm.js'
|
|
2
|
+
import * as Utils from './utils.js'
|
|
3
|
+
import { translate } from './i18n.js'
|
|
4
|
+
|
|
5
|
+
class Rule {
|
|
6
|
+
|
|
7
|
+
get condition() {
|
|
8
|
+
return this._condition
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
set condition(value) {
|
|
12
|
+
this._condition = value
|
|
13
|
+
this.parse()
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
get isDirty() {
|
|
18
|
+
return this._isDirty
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
set isDirty(status) {
|
|
22
|
+
this._isDirty = status
|
|
23
|
+
if (status) this.map.isDirty = status
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
constructor(map, condition = '', options = {}) {
|
|
27
|
+
// TODO make this public properties when browser coverage is ok
|
|
28
|
+
// cf https://caniuse.com/?search=public%20class%20field
|
|
29
|
+
this._condition = null
|
|
30
|
+
this._isDirty = false
|
|
31
|
+
this.OPERATORS = [
|
|
32
|
+
['>', this.gt],
|
|
33
|
+
['<', this.lt],
|
|
34
|
+
// When sent by Django
|
|
35
|
+
['<', this.lt],
|
|
36
|
+
['!=', this.not_equal],
|
|
37
|
+
['=', this.equal],
|
|
38
|
+
]
|
|
39
|
+
this.map = map
|
|
40
|
+
this.active = true
|
|
41
|
+
this.options = options
|
|
42
|
+
this.condition = condition
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
render(fields) {
|
|
46
|
+
this.map.render(fields)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
equal(other) {
|
|
50
|
+
return this.expected === other
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
not_equal(other) {
|
|
54
|
+
return this.expected != other
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
gt(other) {
|
|
58
|
+
return other > this.expected
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
lt(other) {
|
|
62
|
+
return other < this.expected
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
parse() {
|
|
66
|
+
let vars = []
|
|
67
|
+
this.cast = (v) => v
|
|
68
|
+
this.operator = undefined
|
|
69
|
+
for (const [sign, func] of this.OPERATORS) {
|
|
70
|
+
if (this.condition.includes(sign)) {
|
|
71
|
+
this.operator = func
|
|
72
|
+
vars = this.condition.split(sign)
|
|
73
|
+
break
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (vars.length != 2) return
|
|
77
|
+
this.key = vars[0]
|
|
78
|
+
this.expected = vars[1]
|
|
79
|
+
if (!isNaN(this.expected)) this.cast = parseFloat
|
|
80
|
+
else if (['true', 'false'].includes(this.expected)) this.cast = (v) => !!v
|
|
81
|
+
this.expected = this.cast(this.expected)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
match(props) {
|
|
85
|
+
if (!this.operator || !this.active) return false
|
|
86
|
+
return this.operator(this.cast(props[this.key]))
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
getMap() {
|
|
90
|
+
return this.map
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
getOption(option) {
|
|
94
|
+
return this.options[option]
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
edit() {
|
|
98
|
+
const options = [
|
|
99
|
+
[
|
|
100
|
+
'condition',
|
|
101
|
+
{
|
|
102
|
+
handler: 'BlurInput',
|
|
103
|
+
label: translate('Condition'),
|
|
104
|
+
placeholder: translate('key=value or key!=value'),
|
|
105
|
+
},
|
|
106
|
+
],
|
|
107
|
+
'options.color',
|
|
108
|
+
'options.iconClass',
|
|
109
|
+
'options.iconUrl',
|
|
110
|
+
'options.iconOpacity',
|
|
111
|
+
'options.opacity',
|
|
112
|
+
'options.weight',
|
|
113
|
+
'options.fill',
|
|
114
|
+
'options.fillColor',
|
|
115
|
+
'options.fillOpacity',
|
|
116
|
+
'options.smoothFactor',
|
|
117
|
+
'options.dashArray',
|
|
118
|
+
]
|
|
119
|
+
const container = DomUtil.create('div')
|
|
120
|
+
const builder = new U.FormBuilder(this, options)
|
|
121
|
+
const defaultShapeProperties = DomUtil.add('div', '', container)
|
|
122
|
+
defaultShapeProperties.appendChild(builder.build())
|
|
123
|
+
|
|
124
|
+
this.map.editPanel.open({ content: container })
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
renderToolbox(row) {
|
|
128
|
+
row.classList.toggle('off', !this.active)
|
|
129
|
+
const toggle = DomUtil.createButtonIcon(
|
|
130
|
+
row,
|
|
131
|
+
'icon-eye',
|
|
132
|
+
translate('Show/hide layer')
|
|
133
|
+
)
|
|
134
|
+
const edit = DomUtil.createButtonIcon(
|
|
135
|
+
row,
|
|
136
|
+
'icon-edit show-on-edit',
|
|
137
|
+
translate('Edit')
|
|
138
|
+
)
|
|
139
|
+
const remove = DomUtil.createButtonIcon(
|
|
140
|
+
row,
|
|
141
|
+
'icon-delete show-on-edit',
|
|
142
|
+
translate('Delete layer')
|
|
143
|
+
)
|
|
144
|
+
DomEvent.on(edit, 'click', this.edit, this)
|
|
145
|
+
DomEvent.on(
|
|
146
|
+
remove,
|
|
147
|
+
'click',
|
|
148
|
+
function () {
|
|
149
|
+
if (!confirm(translate('Are you sure you want to delete this rule?'))) return
|
|
150
|
+
this._delete()
|
|
151
|
+
this.map.editPanel.close()
|
|
152
|
+
},
|
|
153
|
+
this
|
|
154
|
+
)
|
|
155
|
+
DomUtil.add('span', '', row, this.condition || translate('empty rule'))
|
|
156
|
+
DomUtil.createIcon(row, 'icon-drag', translate('Drag to reorder'))
|
|
157
|
+
row.dataset.id = stamp(this)
|
|
158
|
+
DomEvent.on(toggle, 'click', () => {
|
|
159
|
+
this.active = !this.active
|
|
160
|
+
row.classList.toggle('off', !this.active)
|
|
161
|
+
this.map.render(['rules'])
|
|
162
|
+
})
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
_delete() {
|
|
166
|
+
this.map.rules.rules = this.map.rules.rules.filter((rule) => rule != this)
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export default class Rules {
|
|
171
|
+
constructor(map) {
|
|
172
|
+
this.map = map
|
|
173
|
+
this.rules = []
|
|
174
|
+
this.loadRules()
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
loadRules() {
|
|
178
|
+
if (!this.map.options.rules?.length) return
|
|
179
|
+
for (const { condition, options } of this.map.options.rules) {
|
|
180
|
+
if (!condition) continue
|
|
181
|
+
this.rules.push(new Rule(this.map, condition, options))
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
onReorder(src, dst, initialIndex, finalIndex) {
|
|
186
|
+
const moved = this.rules.find((rule) => stamp(rule) == src.dataset.id)
|
|
187
|
+
const reference = this.rules.find((rule) => stamp(rule) == dst.dataset.id)
|
|
188
|
+
const movedIdx = this.rules.indexOf(moved)
|
|
189
|
+
let referenceIdx = this.rules.indexOf(reference)
|
|
190
|
+
const minIndex = Math.min(movedIdx, referenceIdx)
|
|
191
|
+
const maxIndex = Math.max(movedIdx, referenceIdx)
|
|
192
|
+
moved._delete() // Remove from array
|
|
193
|
+
referenceIdx = this.rules.indexOf(reference)
|
|
194
|
+
let newIdx
|
|
195
|
+
if (finalIndex === 0) newIdx = 0
|
|
196
|
+
else if (finalIndex > initialIndex) newIdx = referenceIdx
|
|
197
|
+
else newIdx = referenceIdx + 1
|
|
198
|
+
this.rules.splice(newIdx, 0, moved)
|
|
199
|
+
moved.isDirty = true
|
|
200
|
+
this.map.render(['rules'])
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
edit(container) {
|
|
204
|
+
const body = DomUtil.createFieldset(container, translate('Conditional style rules'))
|
|
205
|
+
if (this.rules.length) {
|
|
206
|
+
const ul = DomUtil.create('ul', '', body)
|
|
207
|
+
for (const rule of this.rules) {
|
|
208
|
+
rule.renderToolbox(DomUtil.create('li', 'orderable', ul))
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const orderable = new U.Orderable(ul, this.onReorder.bind(this))
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
DomUtil.createButton('umap-add', body, translate('Add rule'), this.addRule, this)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
addRule() {
|
|
218
|
+
const rule = new Rule(this.map)
|
|
219
|
+
rule.isDirty = true
|
|
220
|
+
this.rules.push(rule)
|
|
221
|
+
rule.edit(map)
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
commit() {
|
|
225
|
+
this.map.options.rules = this.rules.map((rule) => {
|
|
226
|
+
return {
|
|
227
|
+
condition: rule.condition,
|
|
228
|
+
options: rule.options,
|
|
229
|
+
}
|
|
230
|
+
})
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
getOption(option, feature) {
|
|
234
|
+
for (const rule of this.rules) {
|
|
235
|
+
if (rule.match(feature.properties)) {
|
|
236
|
+
if (Utils.usableOption(rule.options, option)) return rule.options[option]
|
|
237
|
+
break
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
@@ -1,12 +1,25 @@
|
|
|
1
1
|
import { translate } from './i18n.js'
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
/**
|
|
4
|
+
* This SCHEMA defines metadata about properties.
|
|
5
|
+
*
|
|
6
|
+
* This is here in order to have a centered place where all properties are specified.
|
|
7
|
+
*
|
|
8
|
+
* Each property defines:
|
|
9
|
+
*
|
|
10
|
+
* - `type`: The type of the data
|
|
11
|
+
* - `impacts`: A list of impacts than happen when this property is updated, among
|
|
12
|
+
* 'ui', 'data', 'limit-bounds', 'datalayer-index', 'remote-data',
|
|
13
|
+
* 'background' 'sync'.
|
|
14
|
+
*
|
|
15
|
+
* - Extra keys are being passed to the FormBuilder automatically.
|
|
16
|
+
*/
|
|
5
17
|
|
|
18
|
+
// This is sorted alphabetically
|
|
6
19
|
export const SCHEMA = {
|
|
7
20
|
browsable: {
|
|
8
|
-
impacts: ['ui'],
|
|
9
21
|
type: Boolean,
|
|
22
|
+
impacts: ['ui'],
|
|
10
23
|
},
|
|
11
24
|
captionBar: {
|
|
12
25
|
type: Boolean,
|
|
@@ -14,6 +27,13 @@ export const SCHEMA = {
|
|
|
14
27
|
label: translate('Do you want to display a caption bar?'),
|
|
15
28
|
default: false,
|
|
16
29
|
},
|
|
30
|
+
captionControl: {
|
|
31
|
+
type: Boolean,
|
|
32
|
+
impacts: ['ui'],
|
|
33
|
+
nullable: true,
|
|
34
|
+
label: translate('Display the caption control'),
|
|
35
|
+
default: true,
|
|
36
|
+
},
|
|
17
37
|
captionMenus: {
|
|
18
38
|
type: Boolean,
|
|
19
39
|
impacts: ['ui'],
|
|
@@ -37,6 +57,10 @@ export const SCHEMA = {
|
|
|
37
57
|
type: Object,
|
|
38
58
|
impacts: ['data'],
|
|
39
59
|
},
|
|
60
|
+
condition: {
|
|
61
|
+
type: String,
|
|
62
|
+
impacts: ['data'],
|
|
63
|
+
},
|
|
40
64
|
dashArray: {
|
|
41
65
|
type: String,
|
|
42
66
|
impacts: ['data'],
|
|
@@ -146,6 +170,10 @@ export const SCHEMA = {
|
|
|
146
170
|
label: translate('Display the fullscreen control'),
|
|
147
171
|
default: true,
|
|
148
172
|
},
|
|
173
|
+
geometry: {
|
|
174
|
+
type: Object,
|
|
175
|
+
impacts: ['data'],
|
|
176
|
+
},
|
|
149
177
|
heat: {
|
|
150
178
|
type: Object,
|
|
151
179
|
impacts: ['data'],
|
|
@@ -184,7 +212,6 @@ export const SCHEMA = {
|
|
|
184
212
|
type: Boolean,
|
|
185
213
|
impacts: ['ui'],
|
|
186
214
|
},
|
|
187
|
-
|
|
188
215
|
interactive: {
|
|
189
216
|
type: Boolean,
|
|
190
217
|
impacts: ['data'],
|
|
@@ -272,9 +299,9 @@ export const SCHEMA = {
|
|
|
272
299
|
choices: [
|
|
273
300
|
['none', translate('None')],
|
|
274
301
|
['caption', translate('Caption')],
|
|
275
|
-
['databrowser', translate('Browser
|
|
276
|
-
['datalayers', translate('Browser
|
|
277
|
-
['datafilters', translate('Browser
|
|
302
|
+
['databrowser', translate('Browser: data')],
|
|
303
|
+
['datalayers', translate('Browser: layers')],
|
|
304
|
+
['datafilters', translate('Browser: filters')],
|
|
278
305
|
],
|
|
279
306
|
default: 'none',
|
|
280
307
|
},
|
|
@@ -290,6 +317,7 @@ export const SCHEMA = {
|
|
|
290
317
|
},
|
|
291
318
|
outlink: {
|
|
292
319
|
type: String,
|
|
320
|
+
impacts: [],
|
|
293
321
|
label: translate('Link to…'),
|
|
294
322
|
helpEntries: 'outlink',
|
|
295
323
|
placeholder: 'http://...',
|
|
@@ -362,6 +390,10 @@ export const SCHEMA = {
|
|
|
362
390
|
type: Object,
|
|
363
391
|
impacts: ['remote-data'],
|
|
364
392
|
},
|
|
393
|
+
rules: {
|
|
394
|
+
type: Object,
|
|
395
|
+
impacts: ['data'],
|
|
396
|
+
},
|
|
365
397
|
scaleControl: {
|
|
366
398
|
type: Boolean,
|
|
367
399
|
impacts: ['ui'],
|
|
@@ -373,12 +405,6 @@ export const SCHEMA = {
|
|
|
373
405
|
impacts: ['ui'],
|
|
374
406
|
label: translate('Allow scroll wheel zoom?'),
|
|
375
407
|
},
|
|
376
|
-
captionControl: {
|
|
377
|
-
type: Boolean,
|
|
378
|
-
nullable: true,
|
|
379
|
-
label: translate('Display the caption control'),
|
|
380
|
-
default: true,
|
|
381
|
-
},
|
|
382
408
|
searchControl: {
|
|
383
409
|
type: Boolean,
|
|
384
410
|
impacts: ['ui'],
|
|
@@ -437,6 +463,13 @@ export const SCHEMA = {
|
|
|
437
463
|
inheritable: true,
|
|
438
464
|
default: true,
|
|
439
465
|
},
|
|
466
|
+
syncEnabled: {
|
|
467
|
+
type: Boolean,
|
|
468
|
+
impacts: ['sync', 'ui'],
|
|
469
|
+
label: translate('Enable real-time collaboration'),
|
|
470
|
+
helpEntries: 'sync',
|
|
471
|
+
default: false,
|
|
472
|
+
},
|
|
440
473
|
tilelayer: {
|
|
441
474
|
type: Object,
|
|
442
475
|
impacts: ['background'],
|
|
@@ -453,8 +486,19 @@ export const SCHEMA = {
|
|
|
453
486
|
label: translate('To zoom'),
|
|
454
487
|
helpText: translate('Optional.'),
|
|
455
488
|
},
|
|
489
|
+
ttl: {
|
|
490
|
+
type: Number,
|
|
491
|
+
label: translate('Cache proxied request'),
|
|
492
|
+
choices: [
|
|
493
|
+
['', translate('No cache')],
|
|
494
|
+
['300', translate('5 min')],
|
|
495
|
+
['3600', translate('1 hour')],
|
|
496
|
+
['86400', translate('1 day')],
|
|
497
|
+
],
|
|
498
|
+
default: '300',
|
|
499
|
+
},
|
|
456
500
|
type: {
|
|
457
|
-
type:
|
|
501
|
+
type: String,
|
|
458
502
|
impacts: ['data'],
|
|
459
503
|
},
|
|
460
504
|
weight: {
|
|
@@ -486,4 +530,9 @@ export const SCHEMA = {
|
|
|
486
530
|
label: translate('Default zoom level'),
|
|
487
531
|
inheritable: true,
|
|
488
532
|
},
|
|
533
|
+
// FIXME This is an internal Leaflet property, we might want to do this differently.
|
|
534
|
+
_latlng: {
|
|
535
|
+
type: Object,
|
|
536
|
+
impacts: ['data'],
|
|
537
|
+
},
|
|
489
538
|
}
|