umap-project 3.4.0b2__py3-none-any.whl → 3.4.0b3__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/locale/da/LC_MESSAGES/django.mo +0 -0
- umap/locale/da/LC_MESSAGES/django.po +2 -2
- umap/locale/en/LC_MESSAGES/django.po +14 -14
- umap/migrations/0018_datalayer_uuid.py +1 -1
- umap/models.py +1 -1
- umap/settings/local.py.sample +1 -1
- umap/static/umap/css/form.css +2 -2
- umap/static/umap/js/components/base.js +1 -1
- umap/static/umap/js/modules/browser.js +4 -4
- umap/static/umap/js/modules/data/features.js +1 -1
- umap/static/umap/js/modules/data/fields.js +280 -0
- umap/static/umap/js/modules/data/layer.js +6 -8
- umap/static/umap/js/modules/domutils.js +3 -3
- umap/static/umap/js/modules/filters.js +7 -7
- umap/static/umap/js/modules/help.js +1 -1
- umap/static/umap/js/modules/i18n.js +1 -1
- umap/static/umap/js/modules/importer.js +1 -1
- umap/static/umap/js/modules/managers.js +0 -264
- umap/static/umap/js/modules/rendering/controls.js +4 -2
- umap/static/umap/js/modules/rendering/layers/classified.js +1 -1
- umap/static/umap/js/modules/rendering/layers/heat.js +26 -21
- umap/static/umap/js/modules/rendering/template.js +4 -4
- umap/static/umap/js/modules/ui/dialog.js +10 -1
- umap/static/umap/js/modules/ui/panel.js +2 -2
- umap/static/umap/js/modules/umap.js +9 -4
- umap/static/umap/js/modules/utils.js +1 -1
- umap/static/umap/locale/am_ET.js +14 -5
- umap/static/umap/locale/am_ET.json +14 -5
- umap/static/umap/locale/ar.js +14 -5
- umap/static/umap/locale/ar.json +14 -5
- umap/static/umap/locale/ast.js +14 -5
- umap/static/umap/locale/ast.json +14 -5
- umap/static/umap/locale/bg.js +14 -5
- umap/static/umap/locale/bg.json +14 -5
- umap/static/umap/locale/br.js +1 -1
- umap/static/umap/locale/br.json +1 -1
- umap/static/umap/locale/ca.js +14 -5
- umap/static/umap/locale/ca.json +14 -5
- umap/static/umap/locale/cs_CZ.js +14 -5
- umap/static/umap/locale/cs_CZ.json +14 -5
- umap/static/umap/locale/da.js +48 -39
- umap/static/umap/locale/da.json +48 -39
- umap/static/umap/locale/de.js +14 -5
- umap/static/umap/locale/de.json +14 -5
- umap/static/umap/locale/el.js +14 -5
- umap/static/umap/locale/el.json +14 -5
- umap/static/umap/locale/en.js +14 -5
- umap/static/umap/locale/en.json +14 -5
- umap/static/umap/locale/en_US.json +14 -5
- umap/static/umap/locale/es.js +49 -40
- umap/static/umap/locale/es.json +49 -40
- umap/static/umap/locale/et.js +14 -5
- umap/static/umap/locale/et.json +14 -5
- umap/static/umap/locale/eu.js +14 -5
- umap/static/umap/locale/eu.json +14 -5
- umap/static/umap/locale/fa_IR.js +14 -5
- umap/static/umap/locale/fa_IR.json +14 -5
- umap/static/umap/locale/fi.js +14 -5
- umap/static/umap/locale/fi.json +14 -5
- umap/static/umap/locale/fr.js +14 -5
- umap/static/umap/locale/fr.json +14 -5
- umap/static/umap/locale/gl.js +14 -5
- umap/static/umap/locale/gl.json +14 -5
- umap/static/umap/locale/he.js +14 -5
- umap/static/umap/locale/he.json +14 -5
- umap/static/umap/locale/hr.js +14 -5
- umap/static/umap/locale/hr.json +14 -5
- umap/static/umap/locale/hu.js +56 -47
- umap/static/umap/locale/hu.json +56 -47
- umap/static/umap/locale/id.js +14 -5
- umap/static/umap/locale/id.json +14 -5
- umap/static/umap/locale/is.js +14 -5
- umap/static/umap/locale/is.json +14 -5
- umap/static/umap/locale/it.js +14 -5
- umap/static/umap/locale/it.json +14 -5
- umap/static/umap/locale/ja.js +14 -5
- umap/static/umap/locale/ja.json +14 -5
- umap/static/umap/locale/ko.js +14 -5
- umap/static/umap/locale/ko.json +14 -5
- umap/static/umap/locale/lt.js +14 -5
- umap/static/umap/locale/lt.json +14 -5
- umap/static/umap/locale/ms.js +14 -5
- umap/static/umap/locale/ms.json +14 -5
- umap/static/umap/locale/nl.js +14 -5
- umap/static/umap/locale/nl.json +14 -5
- umap/static/umap/locale/no.js +14 -5
- umap/static/umap/locale/no.json +14 -5
- umap/static/umap/locale/pl.js +14 -5
- umap/static/umap/locale/pl.json +14 -5
- umap/static/umap/locale/pl_PL.json +14 -5
- umap/static/umap/locale/pt.js +14 -5
- umap/static/umap/locale/pt.json +14 -5
- umap/static/umap/locale/pt_BR.js +14 -5
- umap/static/umap/locale/pt_BR.json +14 -5
- umap/static/umap/locale/pt_PT.js +14 -5
- umap/static/umap/locale/pt_PT.json +14 -5
- umap/static/umap/locale/ro.js +14 -5
- umap/static/umap/locale/ro.json +14 -5
- umap/static/umap/locale/ru.js +14 -5
- umap/static/umap/locale/ru.json +14 -5
- umap/static/umap/locale/si.js +1 -1
- umap/static/umap/locale/si.json +1 -1
- umap/static/umap/locale/sk_SK.js +14 -5
- umap/static/umap/locale/sk_SK.json +14 -5
- umap/static/umap/locale/sl.js +14 -5
- umap/static/umap/locale/sl.json +14 -5
- umap/static/umap/locale/sr.js +14 -5
- umap/static/umap/locale/sr.json +14 -5
- umap/static/umap/locale/sv.js +14 -5
- umap/static/umap/locale/sv.json +14 -5
- umap/static/umap/locale/th_TH.js +14 -5
- umap/static/umap/locale/th_TH.json +14 -5
- umap/static/umap/locale/tr.js +14 -5
- umap/static/umap/locale/tr.json +14 -5
- umap/static/umap/locale/uk_UA.js +14 -5
- umap/static/umap/locale/uk_UA.json +14 -5
- umap/static/umap/locale/vi.js +14 -5
- umap/static/umap/locale/vi.json +14 -5
- umap/static/umap/locale/vi_VN.json +14 -5
- umap/static/umap/locale/zh.js +14 -5
- umap/static/umap/locale/zh.json +14 -5
- umap/static/umap/locale/zh_CN.json +14 -5
- umap/static/umap/locale/zh_TW.Big5.json +14 -5
- umap/static/umap/locale/zh_TW.js +47 -38
- umap/static/umap/locale/zh_TW.json +47 -38
- umap/templates/umap/login_popup_end.html +2 -2
- umap/tests/integration/conftest.py +8 -0
- umap/tests/integration/test_anonymous_owned_map.py +1 -1
- umap/tests/integration/test_conditional_rules.py +1 -1
- umap/tests/integration/test_filters.py +6 -7
- umap/tests/integration/test_map_preview.py +1 -1
- umap/tests/integration/test_picto.py +1 -1
- umap/tests/integration/test_save.py +1 -1
- umap/tests/integration/test_websocket_sync.py +63 -19
- umap/tests/test_dashboard.py +1 -1
- umap/tests/test_statics.py +2 -2
- umap/tests/test_views.py +1 -1
- umap/utils.py +1 -1
- {umap_project-3.4.0b2.dist-info → umap_project-3.4.0b3.dist-info}/METADATA +4 -4
- {umap_project-3.4.0b2.dist-info → umap_project-3.4.0b3.dist-info}/RECORD +144 -143
- {umap_project-3.4.0b2.dist-info → umap_project-3.4.0b3.dist-info}/WHEEL +0 -0
- {umap_project-3.4.0b2.dist-info → umap_project-3.4.0b3.dist-info}/entry_points.txt +0 -0
- {umap_project-3.4.0b2.dist-info → umap_project-3.4.0b3.dist-info}/licenses/LICENSE +0 -0
umap/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
VERSION = "3.4.
|
|
1
|
+
VERSION = "3.4.0b3"
|
|
Binary file
|
|
@@ -424,13 +424,13 @@ msgid ""
|
|
|
424
424
|
"Oops, I didn't mean it, I want to <a href=\"%(login_url)s\" "
|
|
425
425
|
"class=\"login\">create an account</a> or <a href=\"%(login_url)s\" "
|
|
426
426
|
"class=\"login\">log in</a> (no worry, we'll attach the map to your account)."
|
|
427
|
-
msgstr ""
|
|
427
|
+
msgstr "Ups, det var ikke meningen, jeg vil gerne <a href=\"%(login_url)s\" class=\"login\">oprette en konto</a> eller <a href=\"%(login_url)s\" class=\"login\">logge ind</a> (bare rolig, vi knytter kortet til din konto)."
|
|
428
428
|
|
|
429
429
|
#: templates/umap/components/alerts/alert.html:35
|
|
430
430
|
msgid ""
|
|
431
431
|
"Yes, I want to continue editing anonymously, I will save the secret edit "
|
|
432
432
|
"link to be able to edit this map later or on another device"
|
|
433
|
-
msgstr ""
|
|
433
|
+
msgstr "Ja, jeg vil fortsætte med at redigere anonymt. Jeg gemmer det hemmelige redigeringslink, så jeg kan redigere dette kort senere eller på en anden enhed."
|
|
434
434
|
|
|
435
435
|
#: templates/umap/components/alerts/alert.html:39
|
|
436
436
|
msgid "Here is your secret link to edit the map, please keep it safe:"
|
|
@@ -8,7 +8,7 @@ msgid ""
|
|
|
8
8
|
msgstr ""
|
|
9
9
|
"Project-Id-Version: PACKAGE VERSION\n"
|
|
10
10
|
"Report-Msgid-Bugs-To: \n"
|
|
11
|
-
"POT-Creation-Date: 2025-10-
|
|
11
|
+
"POT-Creation-Date: 2025-10-17 06:00+0000\n"
|
|
12
12
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
|
13
13
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
|
14
14
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
|
@@ -41,7 +41,7 @@ msgstr ""
|
|
|
41
41
|
msgid "name"
|
|
42
42
|
msgstr ""
|
|
43
43
|
|
|
44
|
-
#: models.py:63 models.py:
|
|
44
|
+
#: models.py:63 models.py:507
|
|
45
45
|
msgid "description"
|
|
46
46
|
msgstr ""
|
|
47
47
|
|
|
@@ -61,23 +61,23 @@ msgstr ""
|
|
|
61
61
|
msgid "Order of the tilelayers in the edit box"
|
|
62
62
|
msgstr ""
|
|
63
63
|
|
|
64
|
-
#: models.py:176 models.py:
|
|
64
|
+
#: models.py:176 models.py:501
|
|
65
65
|
msgid "Only editable with secret edit link"
|
|
66
66
|
msgstr ""
|
|
67
67
|
|
|
68
|
-
#: models.py:177 models.py:
|
|
68
|
+
#: models.py:177 models.py:502
|
|
69
69
|
msgid "Everyone can edit"
|
|
70
70
|
msgstr ""
|
|
71
71
|
|
|
72
|
-
#: models.py:180 models.py:
|
|
72
|
+
#: models.py:180 models.py:495
|
|
73
73
|
msgid "Everyone"
|
|
74
74
|
msgstr ""
|
|
75
75
|
|
|
76
|
-
#: models.py:181 models.py:190 models.py:
|
|
76
|
+
#: models.py:181 models.py:190 models.py:496
|
|
77
77
|
msgid "Editors and team only"
|
|
78
78
|
msgstr ""
|
|
79
79
|
|
|
80
|
-
#: models.py:182 models.py:
|
|
80
|
+
#: models.py:182 models.py:497
|
|
81
81
|
msgid "Owner only"
|
|
82
82
|
msgstr ""
|
|
83
83
|
|
|
@@ -97,7 +97,7 @@ msgstr ""
|
|
|
97
97
|
msgid "Blocked"
|
|
98
98
|
msgstr ""
|
|
99
99
|
|
|
100
|
-
#: models.py:192 models.py:
|
|
100
|
+
#: models.py:192 models.py:491
|
|
101
101
|
msgid "Deleted"
|
|
102
102
|
msgstr ""
|
|
103
103
|
|
|
@@ -137,15 +137,15 @@ msgstr ""
|
|
|
137
137
|
msgid "team"
|
|
138
138
|
msgstr ""
|
|
139
139
|
|
|
140
|
-
#: models.py:230 models.py:
|
|
140
|
+
#: models.py:230 models.py:523
|
|
141
141
|
msgid "edit status"
|
|
142
142
|
msgstr ""
|
|
143
143
|
|
|
144
|
-
#: models.py:235 models.py:
|
|
144
|
+
#: models.py:235 models.py:528
|
|
145
145
|
msgid "share status"
|
|
146
146
|
msgstr ""
|
|
147
147
|
|
|
148
|
-
#: models.py:238 models.py:
|
|
148
|
+
#: models.py:238 models.py:518
|
|
149
149
|
msgid "settings"
|
|
150
150
|
msgstr ""
|
|
151
151
|
|
|
@@ -161,15 +161,15 @@ msgstr ""
|
|
|
161
161
|
msgid "Clone of"
|
|
162
162
|
msgstr ""
|
|
163
163
|
|
|
164
|
-
#: models.py:
|
|
164
|
+
#: models.py:490 models.py:494 models.py:500
|
|
165
165
|
msgid "Inherit"
|
|
166
166
|
msgstr ""
|
|
167
167
|
|
|
168
|
-
#: models.py:
|
|
168
|
+
#: models.py:513
|
|
169
169
|
msgid "display on load"
|
|
170
170
|
msgstr ""
|
|
171
171
|
|
|
172
|
-
#: models.py:
|
|
172
|
+
#: models.py:514
|
|
173
173
|
msgid "Display this layer on load."
|
|
174
174
|
msgstr ""
|
|
175
175
|
|
umap/models.py
CHANGED
|
@@ -289,7 +289,7 @@ class Map(NamedModel):
|
|
|
289
289
|
self.save()
|
|
290
290
|
|
|
291
291
|
def delete(self, **kwargs):
|
|
292
|
-
#
|
|
292
|
+
# Explicitly call datalayers.delete, so we can deal with removing files
|
|
293
293
|
# (the cascade delete would not call the model delete method)
|
|
294
294
|
# Use datalayer_set so to get also the deleted ones.
|
|
295
295
|
for datalayer in self.datalayer_set.all():
|
umap/settings/local.py.sample
CHANGED
|
@@ -59,7 +59,7 @@ SOCIAL_AUTH_REDIRECT_IS_HTTPS = True
|
|
|
59
59
|
SOCIAL_AUTH_RAISE_EXCEPTIONS = False
|
|
60
60
|
SOCIAL_AUTH_BACKEND_ERROR_URL = "/"
|
|
61
61
|
|
|
62
|
-
# If you want to add a
|
|
62
|
+
# If you want to add a playground map, add its primary key
|
|
63
63
|
# UMAP_DEMO_PK = 204
|
|
64
64
|
# If you want to add a showcase map on the home page, add its primary key
|
|
65
65
|
# UMAP_SHOWCASE_PK = 1156
|
umap/static/umap/css/form.css
CHANGED
|
@@ -162,7 +162,6 @@ input[type="submit"] {
|
|
|
162
162
|
background-color: var(--color-mediumGray);
|
|
163
163
|
}
|
|
164
164
|
|
|
165
|
-
.dark .anonymous:hover,
|
|
166
165
|
.dark .button.anonymous:hover,
|
|
167
166
|
.dark [type="button"].anonymous:hover {
|
|
168
167
|
background-color: var(--color-veryDarkViolet);
|
|
@@ -782,12 +781,13 @@ input[type=hidden].blur+[type="button"] {
|
|
|
782
781
|
border-radius: initial;
|
|
783
782
|
}
|
|
784
783
|
|
|
785
|
-
.copiable-input button {
|
|
784
|
+
.copiable-input button[type=button] {
|
|
786
785
|
background-color: var(--background-color);
|
|
787
786
|
color: var(--text-color);
|
|
788
787
|
border: 2px solid var(--color-darkBlue);
|
|
789
788
|
border-left: none;
|
|
790
789
|
width: 40px;
|
|
790
|
+
height: 40px;
|
|
791
791
|
}
|
|
792
792
|
|
|
793
793
|
input.highlightable:not(:placeholder-shown) {
|
|
@@ -12,7 +12,7 @@ export class uMapElement extends HTMLElement {
|
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Retrieves a clone of the content template either using the `template`
|
|
15
|
-
* attribute or an id
|
|
15
|
+
* attribute or an id matching the name of the component:
|
|
16
16
|
*
|
|
17
17
|
* `umap-alert` component => `umap-alert-template` template id lookup.
|
|
18
18
|
*/
|
|
@@ -97,8 +97,8 @@ export default class Browser {
|
|
|
97
97
|
}
|
|
98
98
|
|
|
99
99
|
toggleBadge() {
|
|
100
|
-
Utils.toggleBadge(this.filtersTitle, this.
|
|
101
|
-
Utils.toggleBadge('.umap-control-browse', this.
|
|
100
|
+
Utils.toggleBadge(this.filtersTitle, this.hasActiveFilters())
|
|
101
|
+
Utils.toggleBadge('.umap-control-browse', this.hasActiveFilters())
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
onFormChange() {
|
|
@@ -118,8 +118,8 @@ export default class Browser {
|
|
|
118
118
|
return !!document.querySelector('.on .umap-browser')
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
-
|
|
122
|
-
return !!this.options.filter || this._umap.
|
|
121
|
+
hasActiveFilters() {
|
|
122
|
+
return !!this.options.filter || this._umap.hasActiveFilters()
|
|
123
123
|
}
|
|
124
124
|
|
|
125
125
|
onMoveEnd() {
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
import * as Utils from '../utils.js'
|
|
2
|
+
import { translate } from '../i18n.js'
|
|
3
|
+
import Orderable from '../orderable.js'
|
|
4
|
+
import { uMapAlert as Alert } from '../../components/alerts/alert.js'
|
|
5
|
+
import { Form } from '../form/builder.js'
|
|
6
|
+
|
|
7
|
+
export const getDefaultFields = () => [
|
|
8
|
+
{ key: U.DEFAULT_LABEL_KEY, type: 'String' },
|
|
9
|
+
{ key: 'description', type: 'Text' },
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
export class Fields extends Map {
|
|
13
|
+
constructor(parent, dialog) {
|
|
14
|
+
super()
|
|
15
|
+
this.parent = parent
|
|
16
|
+
this.dialog = dialog
|
|
17
|
+
this.parent.properties.fields ??= []
|
|
18
|
+
this.pull()
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
isDefault() {
|
|
22
|
+
const keys = Array.from(this.keys())
|
|
23
|
+
const defaultKeys = getDefaultFields().map((field) => field.key)
|
|
24
|
+
return (
|
|
25
|
+
keys.length === defaultKeys.length &&
|
|
26
|
+
keys.every((value, index) => value === defaultKeys[index])
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
pull() {
|
|
31
|
+
this.clear()
|
|
32
|
+
for (const field of this.parent.properties.fields) {
|
|
33
|
+
this.add(field)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
push() {
|
|
38
|
+
this.parent.properties.fields = this.all().map((field) => {
|
|
39
|
+
// We don't want to keep the reference, otherwise editing
|
|
40
|
+
// it will also change the old value
|
|
41
|
+
return { ...field }
|
|
42
|
+
})
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async commit() {
|
|
46
|
+
return new Promise((resolve) => {
|
|
47
|
+
const oldFields = Utils.CopyJSON(this.parent.properties.fields)
|
|
48
|
+
resolve()
|
|
49
|
+
this.push()
|
|
50
|
+
this.parent.sync.update(
|
|
51
|
+
'properties.fields',
|
|
52
|
+
this.parent.properties.fields,
|
|
53
|
+
oldFields
|
|
54
|
+
)
|
|
55
|
+
})
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
add(field) {
|
|
59
|
+
if (!field?.key) {
|
|
60
|
+
console.error('Invalid field', field)
|
|
61
|
+
return
|
|
62
|
+
}
|
|
63
|
+
field.type ??= 'String'
|
|
64
|
+
// Copy object, so not to affect original
|
|
65
|
+
// when edited.
|
|
66
|
+
this.set(field.key, { ...field })
|
|
67
|
+
this.push()
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
delete(key) {
|
|
71
|
+
super.delete(key)
|
|
72
|
+
this.push()
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
all() {
|
|
76
|
+
return Array.from(this.values())
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
edit(container) {
|
|
80
|
+
const [root, { ul, add, manageFilters }] = Utils.loadTemplateWithRefs(`
|
|
81
|
+
<details id="fields-management">
|
|
82
|
+
<summary><h4>${translate('Manage Fields')}</h4></summary>
|
|
83
|
+
<fieldset>
|
|
84
|
+
<ul data-ref=ul></ul>
|
|
85
|
+
<div class="button-bar half">
|
|
86
|
+
<button type="button" data-ref=add>${translate('Add a new field')}</button>
|
|
87
|
+
<button type="button" data-ref="manageFilters">${translate('Manage filters')}</button>
|
|
88
|
+
</div>
|
|
89
|
+
</fieldset>
|
|
90
|
+
</details>
|
|
91
|
+
`)
|
|
92
|
+
container.appendChild(root)
|
|
93
|
+
add.hidden = this.parent.isRemoteLayer?.()
|
|
94
|
+
add.addEventListener('click', () => {
|
|
95
|
+
this.editField().then(() => {
|
|
96
|
+
this.parent.edit().then((panel) => {
|
|
97
|
+
panel.scrollTo('details#fields-management')
|
|
98
|
+
})
|
|
99
|
+
})
|
|
100
|
+
})
|
|
101
|
+
manageFilters.addEventListener('click', () => this.parent.filters.edit())
|
|
102
|
+
for (const field of this.all()) {
|
|
103
|
+
const [row, { edit, del, addFilter, editFilter }] = Utils.loadTemplateWithRefs(
|
|
104
|
+
`<li class="orderable with-toolbox" data-key="${field.key}">
|
|
105
|
+
<span>
|
|
106
|
+
<i class="icon icon-16 icon-field-${field.type}" title="${field.type}"></i>
|
|
107
|
+
${field.key}
|
|
108
|
+
</span>
|
|
109
|
+
<span>
|
|
110
|
+
<button class="icon icon-16 icon-edit" title="${translate('Edit this field')}" data-ref=edit></button>
|
|
111
|
+
<button class="icon icon-16 icon-filters" title="${translate('Edit filter')}" data-ref=editFilter></button>
|
|
112
|
+
<button class="icon icon-16 icon-filters-empty" title="${translate('Add a filter for this field')}" data-ref=addFilter></button>
|
|
113
|
+
<button class="icon icon-16 icon-delete" title="${translate('Delete this field')}" data-ref=del></button>
|
|
114
|
+
<i class="icon icon-16 icon-drag" title="${translate('Drag to reorder')}"></i>
|
|
115
|
+
</span>
|
|
116
|
+
</li>`
|
|
117
|
+
)
|
|
118
|
+
editFilter.hidden = !this.parent.filters.has(field.key)
|
|
119
|
+
addFilter.hidden = this.parent.filters.has(field.key)
|
|
120
|
+
del.hidden = this.parent.isRemoteLayer?.()
|
|
121
|
+
editFilter.addEventListener('click', () =>
|
|
122
|
+
this.parent.filters.createFilterForm(field.key)
|
|
123
|
+
)
|
|
124
|
+
addFilter.addEventListener('click', () =>
|
|
125
|
+
this.parent.filters.createFilterForm(field.key)
|
|
126
|
+
)
|
|
127
|
+
ul.appendChild(row)
|
|
128
|
+
edit.addEventListener('click', () => {
|
|
129
|
+
this.editField(field.key).then(() => {
|
|
130
|
+
this.parent.edit().then((panel) => {
|
|
131
|
+
panel.scrollTo('details#fields-management')
|
|
132
|
+
})
|
|
133
|
+
})
|
|
134
|
+
})
|
|
135
|
+
del.addEventListener('click', () => {
|
|
136
|
+
this.confirmDelete(field.key).then(() => {
|
|
137
|
+
this.parent.edit().then((panel) => {
|
|
138
|
+
panel.scrollTo('details#fields-management')
|
|
139
|
+
})
|
|
140
|
+
})
|
|
141
|
+
})
|
|
142
|
+
}
|
|
143
|
+
const onReorder = (src, dst, initialIndex, finalIndex) => {
|
|
144
|
+
const orderedKeys = Array.from(ul.querySelectorAll('li')).map(
|
|
145
|
+
(el) => el.dataset.key
|
|
146
|
+
)
|
|
147
|
+
const oldFields = Utils.CopyJSON(this.parent.properties.fields)
|
|
148
|
+
const copy = Object.fromEntries(this)
|
|
149
|
+
this.clear()
|
|
150
|
+
for (const key of orderedKeys) {
|
|
151
|
+
this.add(copy[key])
|
|
152
|
+
}
|
|
153
|
+
this.parent.sync.update(
|
|
154
|
+
'properties.fields',
|
|
155
|
+
this.parent.properties.fields,
|
|
156
|
+
oldFields
|
|
157
|
+
)
|
|
158
|
+
}
|
|
159
|
+
const orderable = new Orderable(ul, onReorder)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async editField(name) {
|
|
163
|
+
if (!name && this.parent.isRemoteLayer?.()) return
|
|
164
|
+
const FIELD_TYPES = [
|
|
165
|
+
['String', translate('Short text')],
|
|
166
|
+
['Text', translate('Text')],
|
|
167
|
+
['Number', translate('Number')],
|
|
168
|
+
['Date', translate('Date')],
|
|
169
|
+
['Datetime', translate('Date and time')],
|
|
170
|
+
['Enum', translate('List of values')],
|
|
171
|
+
['Boolean', translate('Yes / No')],
|
|
172
|
+
]
|
|
173
|
+
const field = this.get(name) || {}
|
|
174
|
+
const metadatas = [
|
|
175
|
+
[
|
|
176
|
+
'key',
|
|
177
|
+
{
|
|
178
|
+
handler: 'BlurInput',
|
|
179
|
+
label: translate('Field Name'),
|
|
180
|
+
disabled: this.parent.isRemoteLayer?.(),
|
|
181
|
+
},
|
|
182
|
+
],
|
|
183
|
+
[
|
|
184
|
+
'type',
|
|
185
|
+
{
|
|
186
|
+
handler: 'Select',
|
|
187
|
+
selectOptions: FIELD_TYPES,
|
|
188
|
+
label: translate('Field Type'),
|
|
189
|
+
},
|
|
190
|
+
],
|
|
191
|
+
]
|
|
192
|
+
const form = new Form(field, metadatas)
|
|
193
|
+
|
|
194
|
+
const [container, { body, addFilter }] = Utils.loadTemplateWithRefs(`
|
|
195
|
+
<div>
|
|
196
|
+
<h3>${translate('Manage field')}</h3>
|
|
197
|
+
<div data-ref=body></div>
|
|
198
|
+
<button type="button" data-ref=addFilter hidden><i class="icon icon-16 icon-filters"></i>${translate('Add filter for this field')}</button>
|
|
199
|
+
</div>
|
|
200
|
+
`)
|
|
201
|
+
body.appendChild(form.build())
|
|
202
|
+
if (this.parent.filters) {
|
|
203
|
+
addFilter.addEventListener('click', () => {
|
|
204
|
+
this.dialog.accept().then(() => {
|
|
205
|
+
this.parent.filters.createFilterForm(field.key)
|
|
206
|
+
})
|
|
207
|
+
})
|
|
208
|
+
addFilter.hidden = false
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return this.dialog.open({ template: container }).then(() => {
|
|
212
|
+
if (!this.validateName(field.key, field.key !== name)) {
|
|
213
|
+
this.pull()
|
|
214
|
+
return
|
|
215
|
+
}
|
|
216
|
+
this.parent.sync.startBatch()
|
|
217
|
+
const oldFields = Utils.CopyJSON(this.parent.properties.fields)
|
|
218
|
+
if (!name) {
|
|
219
|
+
this.add(field)
|
|
220
|
+
} else if (name !== field.key) {
|
|
221
|
+
this.clear()
|
|
222
|
+
// Keep order on rename
|
|
223
|
+
for (const old of oldFields) {
|
|
224
|
+
if (old.key === name) {
|
|
225
|
+
this.add(field)
|
|
226
|
+
} else {
|
|
227
|
+
this.add(old)
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
this.parent.renameField(name, field.key)
|
|
231
|
+
} else {
|
|
232
|
+
this.push()
|
|
233
|
+
}
|
|
234
|
+
this.parent.sync.update(
|
|
235
|
+
'properties.fields',
|
|
236
|
+
this.parent.properties.fields,
|
|
237
|
+
oldFields
|
|
238
|
+
)
|
|
239
|
+
this.parent.sync.commitBatch()
|
|
240
|
+
this.parent.render(['properties.fields'])
|
|
241
|
+
})
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
validateName(name, isNew = false) {
|
|
245
|
+
if (!name) {
|
|
246
|
+
Alert.error(translate('Name cannot be empty.'))
|
|
247
|
+
return false
|
|
248
|
+
}
|
|
249
|
+
if (name.includes('.')) {
|
|
250
|
+
Alert.error(translate('Name “{name}” should not contain a dot.', { name }))
|
|
251
|
+
return false
|
|
252
|
+
}
|
|
253
|
+
if (isNew && this.has(name)) {
|
|
254
|
+
Alert.error(translate('This name already exists: “{name}”', { name }))
|
|
255
|
+
return false
|
|
256
|
+
}
|
|
257
|
+
return true
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
async confirmDelete(name) {
|
|
261
|
+
return this.dialog
|
|
262
|
+
.confirm(translate('Are you sure you want to delete this field on all the data?'))
|
|
263
|
+
.then(() => {
|
|
264
|
+
this.parent.sync.startBatch()
|
|
265
|
+
const oldFields = Utils.CopyJSON(this.parent.properties.fields)
|
|
266
|
+
this.delete(name)
|
|
267
|
+
this.push()
|
|
268
|
+
if (this.parent.filters.has(name)) {
|
|
269
|
+
this.parent.filters.remove(name)
|
|
270
|
+
}
|
|
271
|
+
this.parent.deleteField(name)
|
|
272
|
+
this.parent.sync.update(
|
|
273
|
+
'properties.fields',
|
|
274
|
+
this.parent.properties.fields,
|
|
275
|
+
oldFields
|
|
276
|
+
)
|
|
277
|
+
this.parent.sync.commitBatch()
|
|
278
|
+
})
|
|
279
|
+
}
|
|
280
|
+
}
|
|
@@ -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 { FeatureManager
|
|
24
|
+
import { FeatureManager } from '../managers.js'
|
|
25
25
|
import { Filters } from '../filters.js'
|
|
26
|
+
import { Fields, getDefaultFields } from './fields.js'
|
|
26
27
|
|
|
27
28
|
export const LAYER_TYPES = [
|
|
28
29
|
DefaultLayer,
|
|
@@ -90,12 +91,9 @@ export class DataLayer {
|
|
|
90
91
|
if (this.showAtLoad()) this.show()
|
|
91
92
|
}
|
|
92
93
|
if (!this._needsFetch && !this._umap.fields.size) {
|
|
93
|
-
this.properties.fields =
|
|
94
|
-
{ key: U.DEFAULT_LABEL_KEY, type: 'String' },
|
|
95
|
-
{ key: 'description', type: 'Text' },
|
|
96
|
-
]
|
|
94
|
+
this.properties.fields = getDefaultFields()
|
|
97
95
|
}
|
|
98
|
-
this.fields = new
|
|
96
|
+
this.fields = new Fields(this, this._umap.dialog)
|
|
99
97
|
this.filters = new Filters(this, this._umap)
|
|
100
98
|
|
|
101
99
|
// Only layers that are displayed on load must be hidden/shown
|
|
@@ -150,7 +148,7 @@ export class DataLayer {
|
|
|
150
148
|
|
|
151
149
|
get fieldKeys() {
|
|
152
150
|
// Needed to get a similar API from layer and uMap, but
|
|
153
|
-
// uMap
|
|
151
|
+
// uMap would return concat of all datalayers fields
|
|
154
152
|
return Array.from(this.fields.keys())
|
|
155
153
|
}
|
|
156
154
|
|
|
@@ -209,7 +207,7 @@ export class DataLayer {
|
|
|
209
207
|
}
|
|
210
208
|
|
|
211
209
|
// This method does a targeted update of the UI,
|
|
212
|
-
// it
|
|
210
|
+
// it would be merged with `render`` method and the
|
|
213
211
|
// SCHEMA at some point
|
|
214
212
|
propagate(fields = []) {
|
|
215
213
|
const impacts = {
|
|
@@ -83,11 +83,11 @@ export const hexToRGB = (hex) => {
|
|
|
83
83
|
.map((x) => Number.parseInt(x, 16))
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
const
|
|
86
|
+
const CACHE_CONTRAST = {}
|
|
87
87
|
export const contrastedColor = (el, bgcolor) => {
|
|
88
88
|
// Return 0 for black and 1 for white
|
|
89
89
|
// bgcolor is a human color, it can be a any keyword (purple…)
|
|
90
|
-
if (typeof
|
|
90
|
+
if (typeof CACHE_CONTRAST[bgcolor] !== 'undefined') return CACHE_CONTRAST[bgcolor]
|
|
91
91
|
let rgb = window.getComputedStyle(el).getPropertyValue('background-color')
|
|
92
92
|
rgb = RGBRegex.exec(rgb)
|
|
93
93
|
if (rgb && rgb.length === 4) {
|
|
@@ -104,6 +104,6 @@ export const contrastedColor = (el, bgcolor) => {
|
|
|
104
104
|
}
|
|
105
105
|
if (!rgb) return 1
|
|
106
106
|
const out = contrastWCAG21(rgb)
|
|
107
|
-
if (bgcolor)
|
|
107
|
+
if (bgcolor) CACHE_CONTRAST[bgcolor] = out
|
|
108
108
|
return out
|
|
109
109
|
}
|
|
@@ -179,7 +179,7 @@ export class Filters {
|
|
|
179
179
|
}
|
|
180
180
|
|
|
181
181
|
isActive() {
|
|
182
|
-
return this.available.values().some((obj) => obj.isActive())
|
|
182
|
+
return Array.from(this.available.values()).some((obj) => obj.isActive())
|
|
183
183
|
}
|
|
184
184
|
|
|
185
185
|
// Loop on the data to compute the list of choices, min
|
|
@@ -306,7 +306,7 @@ export class Filters {
|
|
|
306
306
|
|
|
307
307
|
_listFilters(filters, container, title) {
|
|
308
308
|
const template = `
|
|
309
|
-
<details
|
|
309
|
+
<details>
|
|
310
310
|
<summary>${title}</summary>
|
|
311
311
|
<ul data-ref=ul></ul>
|
|
312
312
|
<div>
|
|
@@ -322,9 +322,8 @@ export class Filters {
|
|
|
322
322
|
`<li>${translate('Add a field prior to create a filter.')}</li>`
|
|
323
323
|
)
|
|
324
324
|
)
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
body.open = false
|
|
325
|
+
} else if (!filters._parent.fields.isDefault()) {
|
|
326
|
+
body.open = true
|
|
328
327
|
}
|
|
329
328
|
filters.available.forEach((filter, fieldKey) => {
|
|
330
329
|
const [li, { edit, remove }] = Utils.loadTemplateWithRefs(
|
|
@@ -442,8 +441,9 @@ export class Filters {
|
|
|
442
441
|
`)
|
|
443
442
|
body.appendChild(form.build())
|
|
444
443
|
editField.addEventListener('click', () => {
|
|
445
|
-
this._umap.dialog.accept()
|
|
446
|
-
|
|
444
|
+
this._umap.dialog.accept().then(() => {
|
|
445
|
+
this._parent.fields.editField(fieldKey)
|
|
446
|
+
})
|
|
447
447
|
})
|
|
448
448
|
|
|
449
449
|
return this._umap.dialog.open({ template: container }).then(() => {
|
|
@@ -118,7 +118,7 @@ const ENTRIES = {
|
|
|
118
118
|
<dt>KML</dt>
|
|
119
119
|
<dd>${translate('Properties imported:')}name, description</dd>
|
|
120
120
|
<dt>CSV</dt>
|
|
121
|
-
<dd>${translate('Comma, tab or semi-colon separated values. SRS WGS84 is implied. Only Point geometries are imported. The import will look at the column headers for any mention of «lat» and «lon» at the
|
|
121
|
+
<dd>${translate('Comma, tab or semi-colon separated values. SRS WGS84 is implied. Only Point geometries are imported. The import will look at the column headers for any mention of «lat» and «lon» at the beginning of the header, case insensitive. All other column are imported as properties.')}</dd>
|
|
122
122
|
<dt>uMap</dt>
|
|
123
123
|
<dd>${translate('Imports all umap data, including layers and settings.')}</dd>
|
|
124
124
|
</div>
|
|
@@ -25,7 +25,7 @@ export function getLocale() {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
// @function translate(string: String, data?: Object): String
|
|
28
|
-
// Actually try to translate the `string`, with
|
|
28
|
+
// Actually try to translate the `string`, with optional variable passed in `data`.
|
|
29
29
|
export function translate(string, data = {}) {
|
|
30
30
|
if (locale && locales[locale] && locales[locale][string] !== undefined) {
|
|
31
31
|
string = locales[locale][string]
|
|
@@ -353,7 +353,7 @@ export default class Importer extends Utils.WithTemplate {
|
|
|
353
353
|
|
|
354
354
|
async copy() {
|
|
355
355
|
// Format may be guessed from file later.
|
|
356
|
-
//
|
|
356
|
+
// Useful in case of multiple files with different formats.
|
|
357
357
|
if (!this.format && !this.files.length) {
|
|
358
358
|
this.onError(translate('Please choose a format'))
|
|
359
359
|
return false
|