umap-project 3.3.2__py3-none-any.whl → 3.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.
- umap/__init__.py +1 -1
- umap/context_processors.py +4 -1
- umap/locale/cs_CZ/LC_MESSAGES/django.mo +0 -0
- umap/locale/cs_CZ/LC_MESSAGES/django.po +43 -33
- umap/locale/da/LC_MESSAGES/django.mo +0 -0
- umap/locale/da/LC_MESSAGES/django.po +43 -33
- umap/locale/de/LC_MESSAGES/django.mo +0 -0
- umap/locale/de/LC_MESSAGES/django.po +64 -53
- umap/locale/el/LC_MESSAGES/django.mo +0 -0
- umap/locale/el/LC_MESSAGES/django.po +35 -29
- umap/locale/en/LC_MESSAGES/django.po +47 -41
- umap/locale/es/LC_MESSAGES/django.mo +0 -0
- umap/locale/es/LC_MESSAGES/django.po +43 -33
- umap/locale/et/LC_MESSAGES/django.mo +0 -0
- umap/locale/et/LC_MESSAGES/django.po +58 -54
- umap/locale/eu/LC_MESSAGES/django.mo +0 -0
- umap/locale/eu/LC_MESSAGES/django.po +43 -33
- umap/locale/fa_IR/LC_MESSAGES/django.mo +0 -0
- umap/locale/fa_IR/LC_MESSAGES/django.po +43 -33
- umap/locale/fr/LC_MESSAGES/django.mo +0 -0
- umap/locale/fr/LC_MESSAGES/django.po +36 -30
- umap/locale/gl/LC_MESSAGES/django.mo +0 -0
- umap/locale/gl/LC_MESSAGES/django.po +43 -33
- umap/locale/hu/LC_MESSAGES/django.mo +0 -0
- umap/locale/hu/LC_MESSAGES/django.po +35 -29
- umap/locale/is/LC_MESSAGES/django.mo +0 -0
- umap/locale/is/LC_MESSAGES/django.po +43 -33
- umap/locale/it/LC_MESSAGES/django.mo +0 -0
- umap/locale/it/LC_MESSAGES/django.po +43 -33
- umap/locale/nl/LC_MESSAGES/django.mo +0 -0
- umap/locale/nl/LC_MESSAGES/django.po +35 -29
- umap/locale/pl/LC_MESSAGES/django.mo +0 -0
- umap/locale/pl/LC_MESSAGES/django.po +114 -103
- umap/locale/pt/LC_MESSAGES/django.mo +0 -0
- umap/locale/pt/LC_MESSAGES/django.po +43 -33
- umap/locale/th_TH/LC_MESSAGES/django.mo +0 -0
- umap/locale/th_TH/LC_MESSAGES/django.po +310 -109
- umap/locale/zh_TW/LC_MESSAGES/django.mo +0 -0
- umap/locale/zh_TW/LC_MESSAGES/django.po +80 -70
- umap/management/commands/switch_user.py +2 -2
- umap/migrations/0018_datalayer_uuid.py +1 -1
- umap/models.py +7 -3
- umap/settings/local.py.sample +1 -1
- umap/static/umap/base.css +89 -32
- umap/static/umap/content.css +129 -33
- umap/static/umap/css/bar.css +82 -20
- umap/static/umap/css/browser.css +163 -0
- umap/static/umap/css/contextmenu.css +15 -0
- umap/static/umap/css/dialog.css +36 -16
- umap/static/umap/css/form.css +123 -33
- umap/static/umap/css/icon.css +46 -3
- umap/static/umap/css/panel.css +7 -3
- umap/static/umap/css/popup.css +34 -8
- umap/static/umap/css/tooltip.css +8 -4
- umap/static/umap/img/16-white.svg +26 -8
- umap/static/umap/img/16.svg +1 -1
- umap/static/umap/img/source/16-white.svg +36 -18
- umap/static/umap/img/source/16.svg +1 -1
- umap/static/umap/js/components/alerts/alert.css +69 -31
- umap/static/umap/js/components/alerts/alert.js +20 -2
- umap/static/umap/js/components/base.js +1 -1
- umap/static/umap/js/modules/browser.js +69 -61
- umap/static/umap/js/modules/caption.js +10 -7
- umap/static/umap/js/modules/data/features.js +89 -63
- umap/static/umap/js/modules/data/fields.js +446 -0
- umap/static/umap/js/modules/data/layer.js +116 -196
- umap/static/umap/js/modules/domutils.js +109 -0
- umap/static/umap/js/modules/filters.js +780 -0
- umap/static/umap/js/modules/form/builder.js +8 -5
- umap/static/umap/js/modules/form/fields.js +111 -221
- umap/static/umap/js/modules/formatter.js +24 -1
- umap/static/umap/js/modules/help.js +4 -3
- umap/static/umap/js/modules/i18n.js +1 -1
- umap/static/umap/js/modules/importer.js +1 -1
- umap/static/umap/js/modules/importers/opendata.js +15 -0
- umap/static/umap/js/modules/importers/openrouteservice.js +6 -1
- umap/static/umap/js/modules/managers.js +2 -1
- umap/static/umap/js/modules/permissions.js +39 -31
- umap/static/umap/js/modules/rendering/controls.js +11 -9
- umap/static/umap/js/modules/rendering/icon.js +3 -8
- umap/static/umap/js/modules/rendering/layers/base.js +3 -3
- umap/static/umap/js/modules/rendering/layers/classified.js +18 -11
- umap/static/umap/js/modules/rendering/layers/cluster.js +23 -11
- umap/static/umap/js/modules/rendering/layers/heat.js +27 -21
- umap/static/umap/js/modules/rendering/map.js +1 -0
- umap/static/umap/js/modules/rendering/template.js +50 -23
- umap/static/umap/js/modules/rendering/ui.js +33 -25
- umap/static/umap/js/modules/rules.js +38 -44
- umap/static/umap/js/modules/schema.js +3 -6
- umap/static/umap/js/modules/share.js +5 -4
- umap/static/umap/js/modules/tableeditor.js +50 -38
- umap/static/umap/js/modules/templates.js +2 -3
- umap/static/umap/js/modules/ui/bar.js +55 -23
- umap/static/umap/js/modules/ui/dialog.js +38 -27
- umap/static/umap/js/modules/ui/panel.js +23 -8
- umap/static/umap/js/modules/ui/tooltip.js +6 -5
- umap/static/umap/js/modules/umap.js +158 -51
- umap/static/umap/js/modules/utils.js +24 -2
- umap/static/umap/js/umap.core.js +1 -110
- umap/static/umap/locale/am_ET.js +52 -17
- umap/static/umap/locale/am_ET.json +52 -17
- umap/static/umap/locale/ar.js +52 -17
- umap/static/umap/locale/ar.json +52 -17
- umap/static/umap/locale/ast.js +52 -17
- umap/static/umap/locale/ast.json +52 -17
- umap/static/umap/locale/bg.js +52 -17
- umap/static/umap/locale/bg.json +52 -17
- umap/static/umap/locale/br.js +48 -22
- umap/static/umap/locale/br.json +48 -22
- umap/static/umap/locale/ca.js +52 -17
- umap/static/umap/locale/ca.json +52 -17
- umap/static/umap/locale/cs_CZ.js +52 -17
- umap/static/umap/locale/cs_CZ.json +52 -17
- umap/static/umap/locale/da.js +54 -17
- umap/static/umap/locale/da.json +54 -17
- umap/static/umap/locale/de.js +102 -67
- umap/static/umap/locale/de.json +102 -67
- umap/static/umap/locale/el.js +52 -17
- umap/static/umap/locale/el.json +52 -17
- umap/static/umap/locale/en.js +54 -16
- umap/static/umap/locale/en.json +54 -16
- umap/static/umap/locale/en_US.json +52 -17
- umap/static/umap/locale/es.js +54 -17
- umap/static/umap/locale/es.json +54 -17
- umap/static/umap/locale/et.js +91 -56
- umap/static/umap/locale/et.json +91 -56
- umap/static/umap/locale/eu.js +84 -49
- umap/static/umap/locale/eu.json +84 -49
- umap/static/umap/locale/fa_IR.js +52 -17
- umap/static/umap/locale/fa_IR.json +52 -17
- umap/static/umap/locale/fi.js +52 -17
- umap/static/umap/locale/fi.json +52 -17
- umap/static/umap/locale/fr.js +54 -17
- umap/static/umap/locale/fr.json +54 -17
- umap/static/umap/locale/gl.js +52 -17
- umap/static/umap/locale/gl.json +52 -17
- umap/static/umap/locale/he.js +52 -17
- umap/static/umap/locale/he.json +52 -17
- umap/static/umap/locale/hr.js +52 -17
- umap/static/umap/locale/hr.json +52 -17
- umap/static/umap/locale/hu.js +59 -24
- umap/static/umap/locale/hu.json +59 -24
- umap/static/umap/locale/id.js +52 -17
- umap/static/umap/locale/id.json +52 -17
- umap/static/umap/locale/is.js +52 -17
- umap/static/umap/locale/is.json +52 -17
- umap/static/umap/locale/it.js +52 -17
- umap/static/umap/locale/it.json +52 -17
- umap/static/umap/locale/ja.js +52 -17
- umap/static/umap/locale/ja.json +52 -17
- umap/static/umap/locale/ko.js +52 -17
- umap/static/umap/locale/ko.json +52 -17
- umap/static/umap/locale/lt.js +52 -17
- umap/static/umap/locale/lt.json +52 -17
- umap/static/umap/locale/ms.js +52 -17
- umap/static/umap/locale/ms.json +52 -17
- umap/static/umap/locale/nl.js +52 -17
- umap/static/umap/locale/nl.json +52 -17
- umap/static/umap/locale/no.js +52 -17
- umap/static/umap/locale/no.json +52 -17
- umap/static/umap/locale/pl.js +53 -17
- umap/static/umap/locale/pl.json +53 -17
- umap/static/umap/locale/pl_PL.json +52 -17
- umap/static/umap/locale/pt.js +52 -17
- umap/static/umap/locale/pt.json +52 -17
- umap/static/umap/locale/pt_BR.js +52 -17
- umap/static/umap/locale/pt_BR.json +52 -17
- umap/static/umap/locale/pt_PT.js +52 -17
- umap/static/umap/locale/pt_PT.json +52 -17
- umap/static/umap/locale/ro.js +52 -17
- umap/static/umap/locale/ro.json +52 -17
- umap/static/umap/locale/ru.js +52 -17
- umap/static/umap/locale/ru.json +52 -17
- umap/static/umap/locale/si.js +1 -1
- umap/static/umap/locale/si.json +1 -1
- umap/static/umap/locale/sk_SK.js +52 -17
- umap/static/umap/locale/sk_SK.json +52 -17
- umap/static/umap/locale/sl.js +52 -17
- umap/static/umap/locale/sl.json +52 -17
- umap/static/umap/locale/sr.js +52 -17
- umap/static/umap/locale/sr.json +52 -17
- umap/static/umap/locale/sv.js +52 -17
- umap/static/umap/locale/sv.json +52 -17
- umap/static/umap/locale/th_TH.js +52 -17
- umap/static/umap/locale/th_TH.json +52 -17
- umap/static/umap/locale/tr.js +52 -17
- umap/static/umap/locale/tr.json +52 -17
- umap/static/umap/locale/uk_UA.js +52 -17
- umap/static/umap/locale/uk_UA.json +52 -17
- umap/static/umap/locale/vi.js +52 -17
- umap/static/umap/locale/vi.json +52 -17
- umap/static/umap/locale/vi_VN.json +52 -17
- umap/static/umap/locale/zh.js +52 -17
- umap/static/umap/locale/zh.json +52 -17
- umap/static/umap/locale/zh_CN.json +52 -17
- umap/static/umap/locale/zh_TW.Big5.json +52 -17
- umap/static/umap/locale/zh_TW.js +53 -17
- umap/static/umap/locale/zh_TW.json +53 -17
- umap/static/umap/map.css +63 -226
- umap/static/umap/unittests/utils.js +18 -0
- umap/static/umap/vars.css +23 -5
- umap/templates/umap/components/alerts/alert.html +32 -29
- umap/templates/umap/css.html +2 -1
- umap/templates/umap/login_popup_end.html +18 -9
- umap/templates/umap/user_map_table.html +7 -2
- umap/tests/integration/conftest.py +10 -6
- umap/tests/integration/test_anonymous_owned_map.py +90 -37
- umap/tests/integration/test_basics.py +25 -1
- umap/tests/integration/test_browser.py +37 -0
- umap/tests/integration/test_cluster.py +110 -0
- umap/tests/integration/test_conditional_rules.py +107 -52
- umap/tests/integration/test_datalayer.py +9 -16
- umap/tests/integration/test_draw_polygon.py +6 -0
- umap/tests/integration/test_draw_polyline.py +11 -0
- umap/tests/integration/test_edit_marker.py +12 -1
- umap/tests/integration/test_export_map.py +19 -0
- umap/tests/integration/test_fields.py +541 -0
- umap/tests/integration/test_filters.py +616 -0
- umap/tests/integration/test_iframe.py +1 -1
- umap/tests/integration/test_import.py +38 -42
- umap/tests/integration/test_map_preview.py +1 -1
- umap/tests/integration/test_picto.py +1 -1
- umap/tests/integration/test_popup.py +31 -0
- umap/tests/integration/test_remote_data.py +60 -4
- umap/tests/integration/test_save.py +1 -1
- umap/tests/integration/test_share.py +4 -4
- umap/tests/integration/test_tableeditor.py +31 -7
- umap/tests/integration/test_websocket_sync.py +71 -20
- umap/tests/test_dashboard.py +11 -1
- umap/tests/test_statics.py +2 -2
- umap/tests/test_utils.py +19 -2
- umap/tests/test_views.py +1 -1
- umap/urls.py +1 -0
- umap/utils.py +8 -1
- umap/views.py +5 -0
- {umap_project-3.3.2.dist-info → umap_project-3.4.0.dist-info}/METADATA +15 -15
- {umap_project-3.3.2.dist-info → umap_project-3.4.0.dist-info}/RECORD +240 -236
- umap/static/umap/js/modules/facets.js +0 -164
- umap/tests/integration/test_facets_browser.py +0 -279
- {umap_project-3.3.2.dist-info → umap_project-3.4.0.dist-info}/WHEEL +0 -0
- {umap_project-3.3.2.dist-info → umap_project-3.4.0.dist-info}/entry_points.txt +0 -0
- {umap_project-3.3.2.dist-info → umap_project-3.4.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
uMapAlert as Alert,
|
|
10
10
|
uMapAlertConflict as AlertConflict,
|
|
11
11
|
} from '../../components/alerts/alert.js'
|
|
12
|
-
import { MutatingForm } from '../form/builder.js'
|
|
12
|
+
import { MutatingForm, Form } from '../form/builder.js'
|
|
13
13
|
import { translate } from '../i18n.js'
|
|
14
14
|
import { DataLayerPermissions } from '../permissions.js'
|
|
15
15
|
import { Default as DefaultLayer } from '../rendering/layers/base.js'
|
|
@@ -21,8 +21,9 @@ import TableEditor from '../tableeditor.js'
|
|
|
21
21
|
import * as Utils from '../utils.js'
|
|
22
22
|
import { LineString, Point, Polygon } from './features.js'
|
|
23
23
|
import Rules from '../rules.js'
|
|
24
|
-
import Orderable from '../orderable.js'
|
|
25
24
|
import { FeatureManager } from '../managers.js'
|
|
25
|
+
import { Filters } from '../filters.js'
|
|
26
|
+
import { Fields, getDefaultFields } from './fields.js'
|
|
26
27
|
|
|
27
28
|
export const LAYER_TYPES = [
|
|
28
29
|
DefaultLayer,
|
|
@@ -83,18 +84,18 @@ export class DataLayer {
|
|
|
83
84
|
}
|
|
84
85
|
this.connectToMap()
|
|
85
86
|
this.permissions = new DataLayerPermissions(this._umap, this)
|
|
86
|
-
this.rules = new Rules(umap, this)
|
|
87
87
|
|
|
88
88
|
this._needsFetch = this.createdOnServer || this.isRemoteLayer()
|
|
89
|
+
if (!this._needsFetch && !this._umap.fields.size) {
|
|
90
|
+
this.properties.fields = getDefaultFields()
|
|
91
|
+
}
|
|
92
|
+
this.fields = new Fields(this, this._umap.dialog)
|
|
93
|
+
this.filters = new Filters(this, this._umap)
|
|
94
|
+
this.rules = new Rules(umap, this)
|
|
95
|
+
|
|
89
96
|
if (!this.createdOnServer) {
|
|
90
97
|
if (this.showAtLoad()) this.show()
|
|
91
98
|
}
|
|
92
|
-
if (!this._needsFetch && !this._umap.fields.length) {
|
|
93
|
-
this.properties.fields = [
|
|
94
|
-
{ key: U.DEFAULT_LABEL_KEY, type: 'String' },
|
|
95
|
-
{ key: 'description', type: 'Text' },
|
|
96
|
-
]
|
|
97
|
-
}
|
|
98
99
|
|
|
99
100
|
// Only layers that are displayed on load must be hidden/shown
|
|
100
101
|
// Automatically, others will be shown manually, and thus will
|
|
@@ -129,7 +130,7 @@ export class DataLayer {
|
|
|
129
130
|
}
|
|
130
131
|
|
|
131
132
|
get cssId() {
|
|
132
|
-
return `datalayer-${
|
|
133
|
+
return `datalayer-${this.id}`
|
|
133
134
|
}
|
|
134
135
|
|
|
135
136
|
get rank() {
|
|
@@ -146,19 +147,6 @@ export class DataLayer {
|
|
|
146
147
|
this.properties.rank = value
|
|
147
148
|
}
|
|
148
149
|
|
|
149
|
-
get fields() {
|
|
150
|
-
if (!this.properties.fields) this.properties.fields = []
|
|
151
|
-
return this.properties.fields
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
set fields(fields) {
|
|
155
|
-
this.properties.fields = fields
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
get fieldKeys() {
|
|
159
|
-
return this.fields.map((field) => field.key)
|
|
160
|
-
}
|
|
161
|
-
|
|
162
150
|
get sortKey() {
|
|
163
151
|
return this.getProperty('sortKey') || U.DEFAULT_LABEL_KEY
|
|
164
152
|
}
|
|
@@ -174,6 +162,18 @@ export class DataLayer {
|
|
|
174
162
|
// Propagate will remove the fields it has already
|
|
175
163
|
// processed
|
|
176
164
|
fields = this.propagate(fields)
|
|
165
|
+
if (fields.includes('properties.fields')) {
|
|
166
|
+
this.fields?.pull()
|
|
167
|
+
if (this._umap.browser.isOpen()) {
|
|
168
|
+
this._umap.browser.buildFilters()
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
if (fields.includes('properties.filters')) {
|
|
172
|
+
this.filters.load()
|
|
173
|
+
if (this._umap.browser.isOpen()) {
|
|
174
|
+
this._umap.browser.buildFilters()
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
177
|
|
|
178
178
|
const impacts = Utils.getImpactsFromSchema(fields)
|
|
179
179
|
|
|
@@ -202,7 +202,7 @@ export class DataLayer {
|
|
|
202
202
|
}
|
|
203
203
|
|
|
204
204
|
// This method does a targeted update of the UI,
|
|
205
|
-
// it
|
|
205
|
+
// it would be merged with `render`` method and the
|
|
206
206
|
// SCHEMA at some point
|
|
207
207
|
propagate(fields = []) {
|
|
208
208
|
const impacts = {
|
|
@@ -225,17 +225,28 @@ export class DataLayer {
|
|
|
225
225
|
}
|
|
226
226
|
|
|
227
227
|
showAtLoad() {
|
|
228
|
-
return this.
|
|
228
|
+
return this.autoVisibility && this.showAtZoom()
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
get autoVisibility() {
|
|
232
|
+
if (this._autoVisibility === undefined) {
|
|
233
|
+
if (this._umap.datalayersFromQueryString) {
|
|
234
|
+
const datalayerIds = this._umap.datalayersFromQueryString
|
|
235
|
+
this._autoVisibility = datalayerIds.includes(this.id.toString())
|
|
236
|
+
if (this.properties.old_id) {
|
|
237
|
+
this._autoVisibility =
|
|
238
|
+
this._autoVisibility ||
|
|
239
|
+
datalayerIds.includes(this.properties.old_id.toString())
|
|
240
|
+
}
|
|
241
|
+
} else {
|
|
242
|
+
this._autoVisibility = this.properties.displayOnLoad
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return this._autoVisibility
|
|
229
246
|
}
|
|
230
247
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
const datalayerIds = this._umap.datalayersFromQueryString
|
|
234
|
-
let loadMe = datalayerIds.includes(this.id.toString())
|
|
235
|
-
if (this.properties.old_id) {
|
|
236
|
-
loadMe = loadMe || datalayerIds.includes(this.properties.old_id.toString())
|
|
237
|
-
}
|
|
238
|
-
return loadMe
|
|
248
|
+
set autoVisibility(value) {
|
|
249
|
+
this._autoVisibility = value
|
|
239
250
|
}
|
|
240
251
|
|
|
241
252
|
insertBefore(other) {
|
|
@@ -304,10 +315,9 @@ export class DataLayer {
|
|
|
304
315
|
|
|
305
316
|
fromGeoJSON(geojson, sync = true) {
|
|
306
317
|
if (!geojson) return []
|
|
307
|
-
const features = this.addData(geojson, sync)
|
|
308
318
|
this._needsFetch = false
|
|
319
|
+
const features = this.addData(geojson, sync)
|
|
309
320
|
this.onDataLoaded()
|
|
310
|
-
this.dataChanged()
|
|
311
321
|
return features
|
|
312
322
|
}
|
|
313
323
|
|
|
@@ -431,14 +441,28 @@ export class DataLayer {
|
|
|
431
441
|
this.layer.removeLayer(feature.ui)
|
|
432
442
|
}
|
|
433
443
|
|
|
434
|
-
addFeature(feature) {
|
|
444
|
+
addFeature(feature, sync = false) {
|
|
435
445
|
feature.connectToDataLayer(this)
|
|
436
446
|
this.features.add(feature)
|
|
437
447
|
this._umap.featuresIndex[feature.getSlug()] = feature
|
|
438
448
|
// TODO: quid for remote data ?
|
|
439
449
|
this.inferFields(feature)
|
|
440
|
-
|
|
450
|
+
try {
|
|
451
|
+
this.showFeature(feature)
|
|
452
|
+
} catch (error) {
|
|
453
|
+
console.error(error)
|
|
454
|
+
if (this._umap.editEnabled) {
|
|
455
|
+
Alert.error(translate('Skipping invalid geometry'))
|
|
456
|
+
}
|
|
457
|
+
console.error('Invalid geometry', feature)
|
|
458
|
+
this.removeFeature(feature, false)
|
|
459
|
+
return
|
|
460
|
+
}
|
|
441
461
|
this.dataChanged()
|
|
462
|
+
if (sync) {
|
|
463
|
+
feature.sync.upsert(feature.toGeoJSON(), null)
|
|
464
|
+
}
|
|
465
|
+
return feature
|
|
442
466
|
}
|
|
443
467
|
|
|
444
468
|
removeFeature(feature, sync) {
|
|
@@ -450,7 +474,9 @@ export class DataLayer {
|
|
|
450
474
|
const oldValue = feature.toGeoJSON()
|
|
451
475
|
feature.sync.delete(oldValue)
|
|
452
476
|
}
|
|
453
|
-
|
|
477
|
+
try {
|
|
478
|
+
this.hideFeature(feature)
|
|
479
|
+
} catch {}
|
|
454
480
|
delete this._umap.featuresIndex[feature.getSlug()]
|
|
455
481
|
feature.disconnectFromDataLayer(this)
|
|
456
482
|
this.features.del(feature)
|
|
@@ -458,96 +484,49 @@ export class DataLayer {
|
|
|
458
484
|
}
|
|
459
485
|
|
|
460
486
|
inferFields(feature) {
|
|
461
|
-
if (!this.properties.fields) this.properties.fields = []
|
|
462
|
-
const keys = this.fieldKeys
|
|
463
487
|
for (const key in feature.properties) {
|
|
464
|
-
if (typeof feature.properties[key] !== 'object') {
|
|
488
|
+
if (key && typeof feature.properties[key] !== 'object') {
|
|
465
489
|
if (key.indexOf('_') === 0) continue
|
|
466
|
-
if (
|
|
467
|
-
this.
|
|
490
|
+
if (this.fields.has(key)) continue
|
|
491
|
+
if (this._umap.fields.has(key)) continue
|
|
492
|
+
let type = 'String'
|
|
493
|
+
if (key === 'description') type = 'Text'
|
|
494
|
+
this.fields.add({ key, type })
|
|
468
495
|
}
|
|
469
496
|
}
|
|
497
|
+
this.fields.push()
|
|
470
498
|
}
|
|
471
499
|
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
.confirm(
|
|
475
|
-
translate('Are you sure you want to delete this field on all the features?')
|
|
476
|
-
)
|
|
477
|
-
.then(() => {
|
|
478
|
-
this.deleteProperty(property)
|
|
479
|
-
})
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
async askForRenameProperty(property) {
|
|
483
|
-
return this._umap.dialog
|
|
484
|
-
.prompt(translate('Please enter the new name of this field'))
|
|
485
|
-
.then(({ prompt }) => {
|
|
486
|
-
if (!prompt || !this.validateName(prompt)) return
|
|
487
|
-
this.renameProperty(property, prompt)
|
|
488
|
-
})
|
|
500
|
+
renameField(oldName, newName) {
|
|
501
|
+
this.renameFeaturesField(oldName, newName)
|
|
489
502
|
}
|
|
490
503
|
|
|
491
|
-
|
|
492
|
-
this.sync.startBatch()
|
|
493
|
-
const oldFields = Utils.CopyJSON(this.fields)
|
|
494
|
-
for (const field of this.fields) {
|
|
495
|
-
if (field.key === oldName) {
|
|
496
|
-
field.key = newName
|
|
497
|
-
break
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
this.sync.update('properties.fields', this.fields, oldFields)
|
|
504
|
+
renameFeaturesField(oldName, newName) {
|
|
501
505
|
this.features.forEach((feature) => {
|
|
502
|
-
feature.
|
|
506
|
+
feature.renameField(oldName, newName)
|
|
503
507
|
})
|
|
504
|
-
this.sync.commitBatch()
|
|
505
508
|
}
|
|
506
509
|
|
|
507
|
-
|
|
508
|
-
this.
|
|
509
|
-
const oldFields = Utils.CopyJSON(this.fields)
|
|
510
|
-
this.fields = this.fields.filter((field) => field.key !== property)
|
|
511
|
-
this.sync.update('properties.fields', this.fields, oldFields)
|
|
512
|
-
this.features.forEach((feature) => {
|
|
513
|
-
feature.deleteProperty(property)
|
|
514
|
-
})
|
|
515
|
-
this.sync.commitBatch()
|
|
510
|
+
deleteField(name) {
|
|
511
|
+
this.deleteFeaturesField(name)
|
|
516
512
|
}
|
|
517
513
|
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
})
|
|
523
|
-
this._umap.dialog
|
|
524
|
-
.prompt(translate('Please enter the name of the property'))
|
|
525
|
-
.then(({ prompt }) => {
|
|
526
|
-
if (!prompt || !this.validateName(prompt)) return
|
|
527
|
-
this.properties.fields.push({ key: prompt, type: 'String' })
|
|
528
|
-
resolve()
|
|
514
|
+
deleteFeaturesField(name) {
|
|
515
|
+
if (!this._umap.fields.has(name) && !this.fields.has(name)) {
|
|
516
|
+
this.features.forEach((feature) => {
|
|
517
|
+
feature.deleteField(name)
|
|
529
518
|
})
|
|
530
|
-
|
|
519
|
+
}
|
|
531
520
|
}
|
|
532
521
|
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
Alert.error(translate('Name “{name}” should not contain a dot.', { name }))
|
|
536
|
-
return false
|
|
537
|
-
}
|
|
538
|
-
if (this.fieldKeys.includes(name)) {
|
|
539
|
-
Alert.error(translate('This name already exists: “{name}”', { name }))
|
|
540
|
-
return false
|
|
541
|
-
}
|
|
542
|
-
return true
|
|
522
|
+
eachFeature(callback) {
|
|
523
|
+
this.features.forEach((feature) => callback(feature))
|
|
543
524
|
}
|
|
544
525
|
|
|
545
|
-
sortedValues(
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
.filter((val, idx, arr) => arr.indexOf(val) === idx)
|
|
550
|
-
.sort(Utils.naturalSort)
|
|
526
|
+
sortedValues(key) {
|
|
527
|
+
const field = this.fields.get(key) || this._umap.fields.get(key)
|
|
528
|
+
if (!field) return []
|
|
529
|
+
return field.values(this.features.all()).sort(Utils.naturalSort)
|
|
551
530
|
}
|
|
552
531
|
|
|
553
532
|
addData(geojson, sync) {
|
|
@@ -601,16 +580,18 @@ export class DataLayer {
|
|
|
601
580
|
|
|
602
581
|
switch (geometry.type) {
|
|
603
582
|
case 'Point':
|
|
604
|
-
// FIXME: deal with MultiPoint
|
|
605
|
-
feature = new Point(this._umap, this, geojson, id)
|
|
606
|
-
break
|
|
607
583
|
case 'MultiPoint':
|
|
608
|
-
if (
|
|
584
|
+
if (
|
|
585
|
+
geometry.coordinates?.length === 1 &&
|
|
586
|
+
Array.isArray(geometry.coordinates?.[0])
|
|
587
|
+
) {
|
|
609
588
|
geojson.geometry.coordinates = geojson.geometry.coordinates[0]
|
|
610
|
-
|
|
611
|
-
} else if (this._umap.editEnabled) {
|
|
589
|
+
geojson.geometry.type = 'Point'
|
|
590
|
+
} else if (geometry.type === 'MultiPoint' && this._umap.editEnabled) {
|
|
612
591
|
Alert.error(translate('Cannot process MultiPoint'))
|
|
592
|
+
break
|
|
613
593
|
}
|
|
594
|
+
feature = new Point(this._umap, this, geojson, id)
|
|
614
595
|
break
|
|
615
596
|
case 'MultiLineString':
|
|
616
597
|
case 'LineString':
|
|
@@ -631,9 +612,7 @@ export class DataLayer {
|
|
|
631
612
|
}
|
|
632
613
|
}
|
|
633
614
|
if (feature && !feature.isEmpty()) {
|
|
634
|
-
this.addFeature(feature)
|
|
635
|
-
if (sync) feature.sync.upsert(feature.toGeoJSON(), null)
|
|
636
|
-
return feature
|
|
615
|
+
return this.addFeature(feature, sync)
|
|
637
616
|
}
|
|
638
617
|
}
|
|
639
618
|
|
|
@@ -735,7 +714,9 @@ export class DataLayer {
|
|
|
735
714
|
}
|
|
736
715
|
|
|
737
716
|
clear(sync = true) {
|
|
717
|
+
this._batch = true
|
|
738
718
|
this.features.forEach((feature) => feature.del(sync))
|
|
719
|
+
this._batch = false
|
|
739
720
|
this.dataChanged()
|
|
740
721
|
}
|
|
741
722
|
|
|
@@ -808,7 +789,7 @@ export class DataLayer {
|
|
|
808
789
|
const builder = new MutatingForm(this, layerFields)
|
|
809
790
|
const template = `
|
|
810
791
|
<details id="layer-properties">
|
|
811
|
-
<summary>${this.layer.getName()}: ${translate('settings')}</summary>
|
|
792
|
+
<summary><h4>${this.layer.getName()}: ${translate('settings')}</h4></summary>
|
|
812
793
|
<fieldset data-ref=fieldset></fieldset>
|
|
813
794
|
</details>
|
|
814
795
|
`
|
|
@@ -902,64 +883,6 @@ export class DataLayer {
|
|
|
902
883
|
fieldset.appendChild(builder.build())
|
|
903
884
|
}
|
|
904
885
|
|
|
905
|
-
_editFields(container) {
|
|
906
|
-
const template = `
|
|
907
|
-
<details id="fields">
|
|
908
|
-
<summary>${translate('Manage Fields')}</summary>
|
|
909
|
-
<fieldset>
|
|
910
|
-
<ul data-ref=ul></ul>
|
|
911
|
-
<button type="button" data-ref=add><i class="icon icon-16 icon-add"></i>${translate('Add a new field')}</button>
|
|
912
|
-
</fieldset>
|
|
913
|
-
</details>
|
|
914
|
-
`
|
|
915
|
-
const [fieldset, { ul, add }] = Utils.loadTemplateWithRefs(template)
|
|
916
|
-
add.addEventListener('click', () => {
|
|
917
|
-
this.addProperty().then(() => {
|
|
918
|
-
this.edit().then((panel) => {
|
|
919
|
-
panel.scrollTo('details#fields')
|
|
920
|
-
})
|
|
921
|
-
})
|
|
922
|
-
})
|
|
923
|
-
container.appendChild(fieldset)
|
|
924
|
-
for (const field of this.fields) {
|
|
925
|
-
const [row, { rename, del }] = Utils.loadTemplateWithRefs(
|
|
926
|
-
`<li class="orderable" data-key="${field.key}">
|
|
927
|
-
<button class="icon icon-16 icon-edit" title="${translate('Rename this field')}" data-ref=rename></button>
|
|
928
|
-
<button class="icon icon-16 icon-delete" title="${translate('Delete this field')}" data-ref=del></button>
|
|
929
|
-
<i class="icon icon-16 icon-drag" title="${translate('Drag to reorder')}"></i>
|
|
930
|
-
${field.key}
|
|
931
|
-
</li>`
|
|
932
|
-
)
|
|
933
|
-
ul.appendChild(row)
|
|
934
|
-
rename.addEventListener('click', () => {
|
|
935
|
-
this.askForRenameProperty(field.key).then(() => {
|
|
936
|
-
this.edit().then((panel) => {
|
|
937
|
-
panel.scrollTo('details#fields')
|
|
938
|
-
})
|
|
939
|
-
})
|
|
940
|
-
})
|
|
941
|
-
del.addEventListener('click', () => {
|
|
942
|
-
this.confirmDeleteProperty(field.key).then(() => {
|
|
943
|
-
this.edit().then((panel) => {
|
|
944
|
-
panel.scrollTo('details#fields')
|
|
945
|
-
})
|
|
946
|
-
})
|
|
947
|
-
})
|
|
948
|
-
}
|
|
949
|
-
const onReorder = (src, dst, initialIndex, finalIndex) => {
|
|
950
|
-
const orderedKeys = Array.from(ul.querySelectorAll('li')).map(
|
|
951
|
-
(el) => el.dataset.key
|
|
952
|
-
)
|
|
953
|
-
const oldFields = Utils.CopyJSON(this.properties.fields)
|
|
954
|
-
this.properties.fields.sort(
|
|
955
|
-
(fieldA, fieldB) =>
|
|
956
|
-
orderedKeys.indexOf(fieldA.key) > orderedKeys.indexOf(fieldB.key)
|
|
957
|
-
)
|
|
958
|
-
this.sync.update('properties.fields', this.properties.fields, oldFields)
|
|
959
|
-
}
|
|
960
|
-
const orderable = new Orderable(ul, onReorder)
|
|
961
|
-
}
|
|
962
|
-
|
|
963
886
|
_editRemoteDataProperties(container) {
|
|
964
887
|
// XXX I'm not sure **why** this is needed (as it's set during `this.initialize`)
|
|
965
888
|
// but apparently it's needed.
|
|
@@ -1070,9 +993,7 @@ export class DataLayer {
|
|
|
1070
993
|
this._editInteractionProperties(container)
|
|
1071
994
|
this._editTextPathProperties(container)
|
|
1072
995
|
this._editRemoteDataProperties(container)
|
|
1073
|
-
|
|
1074
|
-
this._editFields(container)
|
|
1075
|
-
}
|
|
996
|
+
this.fields.edit(container)
|
|
1076
997
|
this.rules.edit(container)
|
|
1077
998
|
|
|
1078
999
|
if (this._umap.properties.urls.datalayer_versions) {
|
|
@@ -1197,7 +1118,7 @@ export class DataLayer {
|
|
|
1197
1118
|
// From now on, do not try to how/hide
|
|
1198
1119
|
// automatically this layer, as user
|
|
1199
1120
|
// has taken control on this.
|
|
1200
|
-
this.
|
|
1121
|
+
this.autoVisibility = false
|
|
1201
1122
|
let display = force
|
|
1202
1123
|
if (force === undefined) {
|
|
1203
1124
|
if (!this.isVisible()) display = true
|
|
@@ -1268,7 +1189,8 @@ export class DataLayer {
|
|
|
1268
1189
|
}
|
|
1269
1190
|
|
|
1270
1191
|
umapGeoJSON() {
|
|
1271
|
-
const
|
|
1192
|
+
const features = this.isRemoteLayer() ? [] : this.features.all()
|
|
1193
|
+
const geojson = this._umap.formatter.toFeatureCollection(features)
|
|
1272
1194
|
geojson._umap_options = this.properties
|
|
1273
1195
|
return geojson
|
|
1274
1196
|
}
|
|
@@ -1305,12 +1227,12 @@ export class DataLayer {
|
|
|
1305
1227
|
async save() {
|
|
1306
1228
|
if (this.isDeleted) return await this.saveDelete()
|
|
1307
1229
|
if (!this.isRemoteLayer() && !this.isLoaded()) return
|
|
1308
|
-
const geojson = this.umapGeoJSON()
|
|
1309
1230
|
const formData = new FormData()
|
|
1310
1231
|
formData.append('name', this.properties.name)
|
|
1311
1232
|
formData.append('display_on_load', !!this.properties.displayOnLoad)
|
|
1312
1233
|
formData.append('rank', this.rank)
|
|
1313
1234
|
formData.append('settings', this.prepareProperties())
|
|
1235
|
+
const geojson = this.umapGeoJSON()
|
|
1314
1236
|
// Filename support is shaky, don't do it for now.
|
|
1315
1237
|
const blob = new Blob([JSON.stringify(geojson)], { type: 'application/json' })
|
|
1316
1238
|
formData.append('geojson', blob)
|
|
@@ -1412,13 +1334,15 @@ export class DataLayer {
|
|
|
1412
1334
|
)) {
|
|
1413
1335
|
container.innerHTML = ''
|
|
1414
1336
|
if (this.layer.renderLegend) return this.layer.renderLegend(container)
|
|
1415
|
-
const keys = new Set(this.fieldKeys)
|
|
1416
1337
|
const rules = new Map()
|
|
1417
1338
|
for (const rule of this.rules) {
|
|
1418
1339
|
rules.set(rule.condition, rule)
|
|
1419
1340
|
}
|
|
1420
1341
|
for (const rule of this._umap.rules) {
|
|
1421
|
-
if (
|
|
1342
|
+
if (
|
|
1343
|
+
!rules.has(rule.condition) &&
|
|
1344
|
+
(this.fields.has(rule.field.key) || this._umap.fields.has(rule.field.key))
|
|
1345
|
+
) {
|
|
1422
1346
|
rules.set(rule.condition, rule)
|
|
1423
1347
|
}
|
|
1424
1348
|
}
|
|
@@ -1442,6 +1366,11 @@ export class DataLayer {
|
|
|
1442
1366
|
'icon-eye',
|
|
1443
1367
|
translate('Show/hide layer')
|
|
1444
1368
|
)
|
|
1369
|
+
const table = DomUtil.createButtonIcon(
|
|
1370
|
+
container,
|
|
1371
|
+
'icon-table show-on-edit',
|
|
1372
|
+
translate('Edit properties in a table')
|
|
1373
|
+
)
|
|
1445
1374
|
const zoomTo = DomUtil.createButtonIcon(
|
|
1446
1375
|
container,
|
|
1447
1376
|
'icon-zoom',
|
|
@@ -1452,11 +1381,6 @@ export class DataLayer {
|
|
|
1452
1381
|
'icon-edit show-on-edit',
|
|
1453
1382
|
translate('Edit')
|
|
1454
1383
|
)
|
|
1455
|
-
const table = DomUtil.createButtonIcon(
|
|
1456
|
-
container,
|
|
1457
|
-
'icon-table show-on-edit',
|
|
1458
|
-
translate('Edit properties in a table')
|
|
1459
|
-
)
|
|
1460
1384
|
const remove = DomUtil.createButtonIcon(
|
|
1461
1385
|
container,
|
|
1462
1386
|
'icon-delete show-on-edit',
|
|
@@ -1474,16 +1398,12 @@ export class DataLayer {
|
|
|
1474
1398
|
}
|
|
1475
1399
|
DomEvent.on(toggle, 'click', () => this.toggle())
|
|
1476
1400
|
DomEvent.on(zoomTo, 'click', this.zoomTo, this)
|
|
1477
|
-
container.classList.add(this.
|
|
1401
|
+
container.classList.add(this.cssId)
|
|
1478
1402
|
container.classList.toggle('off', !this.isVisible())
|
|
1479
1403
|
}
|
|
1480
1404
|
|
|
1481
1405
|
getHidableElements() {
|
|
1482
|
-
return document.querySelectorAll(`.${this.
|
|
1483
|
-
}
|
|
1484
|
-
|
|
1485
|
-
getHidableClass() {
|
|
1486
|
-
return `show_with_datalayer_${stamp(this)}`
|
|
1406
|
+
return document.querySelectorAll(`.${this.cssId}`)
|
|
1487
1407
|
}
|
|
1488
1408
|
|
|
1489
1409
|
propagateDelete() {
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
// Utils that needs the DOM
|
|
2
|
+
import * as Utils from './utils.js'
|
|
3
|
+
import { translate } from './i18n.js'
|
|
4
|
+
import Tooltip from './ui/tooltip.js'
|
|
5
|
+
|
|
6
|
+
export const copyToClipboard = (textToCopy) => {
|
|
7
|
+
const tooltip = new Tooltip()
|
|
8
|
+
// https://stackoverflow.com/a/65996386
|
|
9
|
+
// Navigator clipboard api needs a secure context (https)
|
|
10
|
+
if (navigator.clipboard && window.isSecureContext) {
|
|
11
|
+
navigator.clipboard.writeText(textToCopy)
|
|
12
|
+
} else {
|
|
13
|
+
// Use the 'out of viewport hidden text area' trick
|
|
14
|
+
const textArea = document.createElement('textarea')
|
|
15
|
+
textArea.value = textToCopy
|
|
16
|
+
|
|
17
|
+
// Move textarea out of the viewport so it's not visible
|
|
18
|
+
textArea.style.position = 'absolute'
|
|
19
|
+
textArea.style.left = '-999999px'
|
|
20
|
+
|
|
21
|
+
document.body.prepend(textArea)
|
|
22
|
+
textArea.select()
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
document.execCommand('copy')
|
|
26
|
+
} catch (error) {
|
|
27
|
+
console.error(error)
|
|
28
|
+
} finally {
|
|
29
|
+
textArea.remove()
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
tooltip.open({ content: translate('✅ Copied!'), duration: 5000 })
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const copiableInput = (parent, label, value) => {
|
|
36
|
+
const [container, { input, button }] = Utils.loadTemplateWithRefs(`
|
|
37
|
+
<div class="copiable-input">
|
|
38
|
+
<label>${label}<input type="text" readOnly value="${value}" data-ref=input /></label>
|
|
39
|
+
<button type="button" class="icon icon-24 icon-copy" title="${translate('copy')}" data-ref=button></button>
|
|
40
|
+
</div>
|
|
41
|
+
`)
|
|
42
|
+
button.addEventListener('click', () => copyToClipboard(input.value))
|
|
43
|
+
parent.appendChild(container)
|
|
44
|
+
return input
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// From https://gist.github.com/Accudio/b9cb16e0e3df858cef0d31e38f1fe46f
|
|
48
|
+
// convert colour in range 0-255 to the modifier used within luminance calculation
|
|
49
|
+
const colourMod = (colour) => {
|
|
50
|
+
const sRGB = colour / 255
|
|
51
|
+
let mod = ((sRGB + 0.055) / 1.055) ** 2.4
|
|
52
|
+
if (sRGB < 0.03928) mod = sRGB / 12.92
|
|
53
|
+
return mod
|
|
54
|
+
}
|
|
55
|
+
const RGBRegex = /rgb *\( *([0-9]{1,3}) *, *([0-9]{1,3}) *, *([0-9]{1,3}) *\)/
|
|
56
|
+
|
|
57
|
+
export const textColorFromBackgroundColor = (el, bgcolor) => {
|
|
58
|
+
return contrastedColor(el, bgcolor) ? '#ffffff' : '#000000'
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const contrastWCAG21 = (rgb) => {
|
|
62
|
+
const [r, g, b] = rgb
|
|
63
|
+
// luminance of inputted colour
|
|
64
|
+
const lum = 0.2126 * colourMod(r) + 0.7152 * colourMod(g) + 0.0722 * colourMod(b)
|
|
65
|
+
// white has a luminance of 1
|
|
66
|
+
const whiteLum = 1
|
|
67
|
+
const contrast = (whiteLum + 0.05) / (lum + 0.05)
|
|
68
|
+
return contrast > 3 ? 1 : 0
|
|
69
|
+
}
|
|
70
|
+
const colorNameToHex = (str) => {
|
|
71
|
+
const ctx = document.createElement('canvas').getContext('2d')
|
|
72
|
+
ctx.fillStyle = str
|
|
73
|
+
return ctx.fillStyle
|
|
74
|
+
}
|
|
75
|
+
export const hexToRGB = (hex) => {
|
|
76
|
+
return hex
|
|
77
|
+
.replace(
|
|
78
|
+
/^#?([a-f\d])([a-f\d])([a-f\d])$/i,
|
|
79
|
+
(m, r, g, b) => `#${r}${r}${g}${g}${b}${b}`
|
|
80
|
+
)
|
|
81
|
+
.substring(1)
|
|
82
|
+
.match(/.{2}/g)
|
|
83
|
+
.map((x) => Number.parseInt(x, 16))
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const CACHE_CONTRAST = {}
|
|
87
|
+
export const contrastedColor = (el, bgcolor) => {
|
|
88
|
+
// Return 0 for black and 1 for white
|
|
89
|
+
// bgcolor is a human color, it can be a any keyword (purple…)
|
|
90
|
+
if (typeof CACHE_CONTRAST[bgcolor] !== 'undefined') return CACHE_CONTRAST[bgcolor]
|
|
91
|
+
let rgb = window.getComputedStyle(el).getPropertyValue('background-color')
|
|
92
|
+
rgb = RGBRegex.exec(rgb)
|
|
93
|
+
if (rgb && rgb.length === 4) {
|
|
94
|
+
rgb = [
|
|
95
|
+
Number.parseInt(rgb[1], 10),
|
|
96
|
+
Number.parseInt(rgb[2], 10),
|
|
97
|
+
Number.parseInt(rgb[3], 10),
|
|
98
|
+
]
|
|
99
|
+
} else {
|
|
100
|
+
// The element may not yet be added to the DOM, so let's try
|
|
101
|
+
// another way
|
|
102
|
+
const hex = colorNameToHex(bgcolor)
|
|
103
|
+
rgb = hexToRGB(hex)
|
|
104
|
+
}
|
|
105
|
+
if (!rgb) return 1
|
|
106
|
+
const out = contrastWCAG21(rgb)
|
|
107
|
+
if (bgcolor) CACHE_CONTRAST[bgcolor] = out
|
|
108
|
+
return out
|
|
109
|
+
}
|