umap-project 3.3.6__py3-none-any.whl → 3.4.0b0__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/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 +35 -29
- 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 +34 -28
- 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 +43 -33
- 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/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 +122 -32
- 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/modules/browser.js +63 -55
- umap/static/umap/js/modules/caption.js +10 -7
- umap/static/umap/js/modules/data/features.js +82 -59
- umap/static/umap/js/modules/data/layer.js +56 -157
- umap/static/umap/js/modules/domutils.js +109 -0
- umap/static/umap/js/modules/filters.js +807 -0
- umap/static/umap/js/modules/form/builder.js +8 -5
- umap/static/umap/js/modules/form/fields.js +110 -220
- umap/static/umap/js/modules/formatter.js +24 -1
- umap/static/umap/js/modules/help.js +3 -2
- umap/static/umap/js/modules/importers/opendata.js +5 -0
- umap/static/umap/js/modules/importers/openrouteservice.js +6 -1
- umap/static/umap/js/modules/managers.js +265 -1
- umap/static/umap/js/modules/permissions.js +35 -31
- umap/static/umap/js/modules/rendering/controls.js +7 -7
- umap/static/umap/js/modules/rendering/icon.js +3 -8
- umap/static/umap/js/modules/rendering/layers/classified.js +17 -10
- umap/static/umap/js/modules/rendering/layers/cluster.js +2 -2
- umap/static/umap/js/modules/rendering/template.js +44 -8
- umap/static/umap/js/modules/rendering/ui.js +29 -23
- umap/static/umap/js/modules/rules.js +4 -3
- umap/static/umap/js/modules/schema.js +3 -6
- umap/static/umap/js/modules/share.js +4 -3
- 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 +42 -18
- umap/static/umap/js/modules/ui/dialog.js +33 -31
- umap/static/umap/js/modules/ui/panel.js +21 -7
- umap/static/umap/js/modules/ui/tooltip.js +6 -5
- umap/static/umap/js/modules/umap.js +148 -51
- umap/static/umap/js/modules/utils.js +23 -1
- umap/static/umap/js/umap.core.js +1 -110
- umap/static/umap/locale/am_ET.js +40 -14
- umap/static/umap/locale/am_ET.json +40 -14
- umap/static/umap/locale/ar.js +40 -14
- umap/static/umap/locale/ar.json +40 -14
- umap/static/umap/locale/ast.js +40 -14
- umap/static/umap/locale/ast.json +40 -14
- umap/static/umap/locale/bg.js +40 -14
- umap/static/umap/locale/bg.json +40 -14
- umap/static/umap/locale/br.js +47 -21
- umap/static/umap/locale/br.json +47 -21
- umap/static/umap/locale/ca.js +40 -14
- umap/static/umap/locale/ca.json +40 -14
- umap/static/umap/locale/cs_CZ.js +40 -14
- umap/static/umap/locale/cs_CZ.json +40 -14
- umap/static/umap/locale/da.js +40 -14
- umap/static/umap/locale/da.json +40 -14
- umap/static/umap/locale/de.js +39 -13
- umap/static/umap/locale/de.json +39 -13
- umap/static/umap/locale/el.js +40 -14
- umap/static/umap/locale/el.json +40 -14
- umap/static/umap/locale/en.js +39 -13
- umap/static/umap/locale/en.json +39 -13
- umap/static/umap/locale/en_US.json +40 -14
- umap/static/umap/locale/es.js +40 -14
- umap/static/umap/locale/es.json +40 -14
- umap/static/umap/locale/et.js +79 -53
- umap/static/umap/locale/et.json +79 -53
- umap/static/umap/locale/eu.js +72 -46
- umap/static/umap/locale/eu.json +72 -46
- umap/static/umap/locale/fa_IR.js +40 -14
- umap/static/umap/locale/fa_IR.json +40 -14
- umap/static/umap/locale/fi.js +40 -14
- umap/static/umap/locale/fi.json +40 -14
- umap/static/umap/locale/fr.js +39 -13
- umap/static/umap/locale/fr.json +39 -13
- umap/static/umap/locale/gl.js +40 -14
- umap/static/umap/locale/gl.json +40 -14
- umap/static/umap/locale/he.js +40 -14
- umap/static/umap/locale/he.json +40 -14
- umap/static/umap/locale/hr.js +40 -14
- umap/static/umap/locale/hr.json +40 -14
- umap/static/umap/locale/hu.js +40 -14
- umap/static/umap/locale/hu.json +40 -14
- umap/static/umap/locale/id.js +40 -14
- umap/static/umap/locale/id.json +40 -14
- umap/static/umap/locale/is.js +40 -14
- umap/static/umap/locale/is.json +40 -14
- umap/static/umap/locale/it.js +40 -14
- umap/static/umap/locale/it.json +40 -14
- umap/static/umap/locale/ja.js +40 -14
- umap/static/umap/locale/ja.json +40 -14
- umap/static/umap/locale/ko.js +40 -14
- umap/static/umap/locale/ko.json +40 -14
- umap/static/umap/locale/lt.js +40 -14
- umap/static/umap/locale/lt.json +40 -14
- umap/static/umap/locale/ms.js +40 -14
- umap/static/umap/locale/ms.json +40 -14
- umap/static/umap/locale/nl.js +40 -14
- umap/static/umap/locale/nl.json +40 -14
- umap/static/umap/locale/no.js +40 -14
- umap/static/umap/locale/no.json +40 -14
- umap/static/umap/locale/pl.js +40 -14
- umap/static/umap/locale/pl.json +40 -14
- umap/static/umap/locale/pl_PL.json +40 -14
- umap/static/umap/locale/pt.js +40 -14
- umap/static/umap/locale/pt.json +40 -14
- umap/static/umap/locale/pt_BR.js +40 -14
- umap/static/umap/locale/pt_BR.json +40 -14
- umap/static/umap/locale/pt_PT.js +40 -14
- umap/static/umap/locale/pt_PT.json +40 -14
- umap/static/umap/locale/ro.js +40 -14
- umap/static/umap/locale/ro.json +40 -14
- umap/static/umap/locale/ru.js +40 -14
- umap/static/umap/locale/ru.json +40 -14
- umap/static/umap/locale/sk_SK.js +40 -14
- umap/static/umap/locale/sk_SK.json +40 -14
- umap/static/umap/locale/sl.js +40 -14
- umap/static/umap/locale/sl.json +40 -14
- umap/static/umap/locale/sr.js +40 -14
- umap/static/umap/locale/sr.json +40 -14
- umap/static/umap/locale/sv.js +40 -14
- umap/static/umap/locale/sv.json +40 -14
- umap/static/umap/locale/th_TH.js +40 -14
- umap/static/umap/locale/th_TH.json +40 -14
- umap/static/umap/locale/tr.js +40 -14
- umap/static/umap/locale/tr.json +40 -14
- umap/static/umap/locale/uk_UA.js +40 -14
- umap/static/umap/locale/uk_UA.json +40 -14
- umap/static/umap/locale/vi.js +40 -14
- umap/static/umap/locale/vi.json +40 -14
- umap/static/umap/locale/vi_VN.json +40 -14
- umap/static/umap/locale/zh.js +40 -14
- umap/static/umap/locale/zh.json +40 -14
- umap/static/umap/locale/zh_CN.json +40 -14
- umap/static/umap/locale/zh_TW.Big5.json +40 -14
- umap/static/umap/locale/zh_TW.js +39 -13
- umap/static/umap/locale/zh_TW.json +39 -13
- umap/static/umap/map.css +60 -223
- 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 +2 -6
- umap/tests/integration/test_anonymous_owned_map.py +89 -36
- umap/tests/integration/test_basics.py +25 -1
- umap/tests/integration/test_browser.py +37 -0
- umap/tests/integration/test_draw_polygon.py +2 -0
- umap/tests/integration/test_edit_marker.py +1 -1
- umap/tests/integration/test_export_map.py +19 -0
- umap/tests/integration/test_fields.py +522 -0
- umap/tests/integration/test_filters.py +617 -0
- umap/tests/integration/test_import.py +15 -42
- umap/tests/integration/test_remote_data.py +60 -4
- umap/tests/integration/test_share.py +4 -4
- umap/tests/integration/test_tableeditor.py +31 -7
- umap/tests/integration/test_websocket_sync.py +3 -1
- umap/tests/test_dashboard.py +10 -0
- umap/urls.py +1 -0
- umap/views.py +5 -0
- {umap_project-3.3.6.dist-info → umap_project-3.4.0b0.dist-info}/METADATA +12 -12
- {umap_project-3.3.6.dist-info → umap_project-3.4.0b0.dist-info}/RECORD +214 -211
- umap/static/umap/js/modules/facets.js +0 -164
- umap/tests/integration/test_facets_browser.py +0 -279
- {umap_project-3.3.6.dist-info → umap_project-3.4.0b0.dist-info}/WHEEL +0 -0
- {umap_project-3.3.6.dist-info → umap_project-3.4.0b0.dist-info}/entry_points.txt +0 -0
- {umap_project-3.3.6.dist-info → umap_project-3.4.0b0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,807 @@
|
|
|
1
|
+
import { translate } from './i18n.js'
|
|
2
|
+
import { Form } from './form/builder.js'
|
|
3
|
+
import * as Utils from './utils.js'
|
|
4
|
+
import Orderable from './orderable.js'
|
|
5
|
+
import { Fields } from './form/fields.js'
|
|
6
|
+
|
|
7
|
+
const EMPTY_VALUE = translate('<empty value>')
|
|
8
|
+
|
|
9
|
+
const getParser = (type) => {
|
|
10
|
+
switch (type) {
|
|
11
|
+
case 'Number':
|
|
12
|
+
return Number.parseFloat
|
|
13
|
+
case 'Datetime':
|
|
14
|
+
return (v) => new Date(v)
|
|
15
|
+
case 'Date':
|
|
16
|
+
return Utils.parseNaiveDate
|
|
17
|
+
case 'Boolean':
|
|
18
|
+
return Boolean
|
|
19
|
+
case 'Enum':
|
|
20
|
+
return (v) => {
|
|
21
|
+
if (!v) return [EMPTY_VALUE]
|
|
22
|
+
return String(v || '')
|
|
23
|
+
.split(',')
|
|
24
|
+
.map((s) => s.trim())
|
|
25
|
+
}
|
|
26
|
+
default:
|
|
27
|
+
return (v) => String(v || '')
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
const Widgets = {}
|
|
31
|
+
|
|
32
|
+
class BaseWidget {
|
|
33
|
+
constructor(parent, label, field) {
|
|
34
|
+
this.parent = parent
|
|
35
|
+
this.label = label
|
|
36
|
+
this.field = field
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
get userData() {
|
|
40
|
+
return this.parent.userData[this.field] || {}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
dumps() {
|
|
44
|
+
const props = {
|
|
45
|
+
widget: this.KEY,
|
|
46
|
+
fieldKey: this.field,
|
|
47
|
+
}
|
|
48
|
+
if (this.label) {
|
|
49
|
+
props.label = this.label
|
|
50
|
+
}
|
|
51
|
+
return props
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
getFormField(field) {
|
|
55
|
+
return 'FilterByCheckbox'
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
computeInitialData(data, value) {}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
Widgets.MinMax = class extends BaseWidget {
|
|
62
|
+
constructor(parent, label, field) {
|
|
63
|
+
super(parent, label, field)
|
|
64
|
+
// FIXME make it dynamic from class name
|
|
65
|
+
this.KEY = 'MinMax'
|
|
66
|
+
}
|
|
67
|
+
match(value) {
|
|
68
|
+
if (this.userData.min > value) return true
|
|
69
|
+
if (this.userData.max < value) return true
|
|
70
|
+
return false
|
|
71
|
+
}
|
|
72
|
+
isActive() {
|
|
73
|
+
return this.userData.min !== undefined || this.userData.max !== undefined
|
|
74
|
+
}
|
|
75
|
+
getFormField(field) {
|
|
76
|
+
if (field.type === 'Number') {
|
|
77
|
+
return 'FilterByNumber'
|
|
78
|
+
}
|
|
79
|
+
if (field.type === 'Date') {
|
|
80
|
+
return 'FilterByDate'
|
|
81
|
+
}
|
|
82
|
+
if (field.type === 'Datetime') {
|
|
83
|
+
return 'FilterByDateTime'
|
|
84
|
+
}
|
|
85
|
+
return super.getFormField(field)
|
|
86
|
+
}
|
|
87
|
+
computeInitialData(data, value) {
|
|
88
|
+
if (value === undefined || value === null) return
|
|
89
|
+
if (data.min === undefined || data.min > value) {
|
|
90
|
+
data.min = value
|
|
91
|
+
}
|
|
92
|
+
if (data.max === undefined || data.max < value) {
|
|
93
|
+
data.max = value
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// Can't use static properties yet (baseline >= 2022)
|
|
98
|
+
Widgets.MinMax.NAME = translate('Min/Max')
|
|
99
|
+
|
|
100
|
+
class Choices extends BaseWidget {
|
|
101
|
+
match(value) {
|
|
102
|
+
if (!this.userData.selected?.length) return false
|
|
103
|
+
if (Array.isArray(value)) {
|
|
104
|
+
const intersection = value.filter((item) => this.userData.selected.includes(item))
|
|
105
|
+
if (intersection.length !== this.userData.selected.length) return true
|
|
106
|
+
} else {
|
|
107
|
+
value = value || EMPTY_VALUE
|
|
108
|
+
if (!this.userData.selected.includes(value)) return true
|
|
109
|
+
}
|
|
110
|
+
return false
|
|
111
|
+
}
|
|
112
|
+
isActive() {
|
|
113
|
+
return !!this.userData.selected?.length
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
computeInitialData(data, value) {
|
|
117
|
+
data.choices ??= new Set()
|
|
118
|
+
if (Array.isArray(value)) {
|
|
119
|
+
data.choices = new Set([...data.choices, ...value])
|
|
120
|
+
} else {
|
|
121
|
+
value = value || EMPTY_VALUE
|
|
122
|
+
data.choices.add(value)
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
Widgets.Checkbox = class extends Choices {
|
|
128
|
+
constructor(parent, label, field) {
|
|
129
|
+
super(parent, label, field)
|
|
130
|
+
this.KEY = 'Checkbox'
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
Widgets.Checkbox.NAME = translate('Multiple choices')
|
|
134
|
+
|
|
135
|
+
Widgets.Radio = class extends Choices {
|
|
136
|
+
constructor(parent, label, field) {
|
|
137
|
+
super(parent, label, field)
|
|
138
|
+
this.KEY = 'Radio'
|
|
139
|
+
}
|
|
140
|
+
getFormField(field) {
|
|
141
|
+
return 'FilterByRadio'
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
Widgets.Radio.NAME = translate('Exclusive choice')
|
|
145
|
+
|
|
146
|
+
Widgets.Switch = class extends BaseWidget {
|
|
147
|
+
constructor(parent, label, field) {
|
|
148
|
+
super(parent, label, field)
|
|
149
|
+
this.KEY = 'Switch'
|
|
150
|
+
}
|
|
151
|
+
match(value) {
|
|
152
|
+
if (this.userData.wanted === undefined) return false
|
|
153
|
+
return !!value !== this.userData.wanted
|
|
154
|
+
}
|
|
155
|
+
isActive() {
|
|
156
|
+
return this.userData.wanted !== undefined
|
|
157
|
+
}
|
|
158
|
+
getFormField(field) {
|
|
159
|
+
return 'FilterBySwitch'
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
Widgets.Switch.NAME = translate('Yes/No')
|
|
163
|
+
|
|
164
|
+
const loadWidget = (key) => {
|
|
165
|
+
return Widgets[key] || Widgets.Checkbox
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export class Filters {
|
|
169
|
+
constructor(parent, umap) {
|
|
170
|
+
this._parent = parent
|
|
171
|
+
this._umap = umap
|
|
172
|
+
this.available = new Map()
|
|
173
|
+
this.userData = {}
|
|
174
|
+
this.load()
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
get size() {
|
|
178
|
+
return this.available.size
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
isActive() {
|
|
182
|
+
return this.available.values().some((obj) => obj.isActive())
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Loop on the data to compute the list of choices, min
|
|
186
|
+
// and max values.
|
|
187
|
+
computeInitialData() {
|
|
188
|
+
const initialData = Object.fromEntries(
|
|
189
|
+
this.available.keys().map((name) => [name, {}])
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
for (const [name, filter] of this.available.entries()) {
|
|
193
|
+
const field = this._parent.fields.get(name)
|
|
194
|
+
if (!field) continue
|
|
195
|
+
const parser = getParser(field.type)
|
|
196
|
+
this._parent.eachFeature((feature) => {
|
|
197
|
+
let value = feature.properties[name]
|
|
198
|
+
value = parser(value)
|
|
199
|
+
filter.computeInitialData(initialData[name], value)
|
|
200
|
+
})
|
|
201
|
+
}
|
|
202
|
+
return initialData
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
buildFormFields() {
|
|
206
|
+
const initialData = this.computeInitialData()
|
|
207
|
+
|
|
208
|
+
const formFields = []
|
|
209
|
+
for (const [name, filter] of this.available.entries()) {
|
|
210
|
+
const field = this._parent.fields.get(name)
|
|
211
|
+
if (!field) continue
|
|
212
|
+
formFields.push([
|
|
213
|
+
`userData.${name}`,
|
|
214
|
+
{
|
|
215
|
+
initialData: initialData[name] || {},
|
|
216
|
+
handler: filter.getFormField(field),
|
|
217
|
+
label: Utils.escapeHTML(this.available.get(name).label || field.key),
|
|
218
|
+
onClick: () => {
|
|
219
|
+
this._parent
|
|
220
|
+
.edit()
|
|
221
|
+
.then((panel) => panel.scrollTo('details#fields-management'))
|
|
222
|
+
this._parent.filters.createFilterForm(name)
|
|
223
|
+
},
|
|
224
|
+
},
|
|
225
|
+
])
|
|
226
|
+
}
|
|
227
|
+
return formFields
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
load() {
|
|
231
|
+
let filters = this._parent.properties.filters || []
|
|
232
|
+
// TMP fix for dev server to update map created before changing
|
|
233
|
+
// filters to be an array
|
|
234
|
+
if (typeof filters === 'object' && !Array.isArray(filters) && filters !== null) {
|
|
235
|
+
filters = Object.entries(filters).map(([fieldKey, props]) => {
|
|
236
|
+
return { fieldKey, ...props }
|
|
237
|
+
})
|
|
238
|
+
}
|
|
239
|
+
for (const filter of filters) {
|
|
240
|
+
this._add({ ...filter })
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
dumps(sync = true) {
|
|
245
|
+
const oldValue = this._parent.properties.filters
|
|
246
|
+
this._parent.properties.filters = Array.from(
|
|
247
|
+
this.available.entries().map(([key, filter]) => filter.dumps())
|
|
248
|
+
)
|
|
249
|
+
if (sync) {
|
|
250
|
+
this._parent.sync.update(
|
|
251
|
+
'properties.filters',
|
|
252
|
+
this._parent.properties.filters,
|
|
253
|
+
oldValue
|
|
254
|
+
)
|
|
255
|
+
this._parent.render(['properties.filters'])
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
has(fieldKey) {
|
|
260
|
+
return this.available.has(fieldKey)
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
get(fieldKey) {
|
|
264
|
+
return this.available.get(fieldKey)
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
add({ fieldKey, label, widget }) {
|
|
268
|
+
if (!this.available.has(fieldKey)) {
|
|
269
|
+
this.update({ fieldKey, label, widget })
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
_add({ fieldKey, label, widget }) {
|
|
274
|
+
const klass = loadWidget(widget)
|
|
275
|
+
const inst = new klass(this, label, fieldKey)
|
|
276
|
+
this.available.set(fieldKey, inst)
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
update({ fieldKey, label, widget }) {
|
|
280
|
+
this._add({ fieldKey, label, widget })
|
|
281
|
+
this.dumps()
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
remove(fieldKey) {
|
|
285
|
+
this.available.delete(fieldKey)
|
|
286
|
+
this.dumps()
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
edit() {
|
|
290
|
+
const template = `
|
|
291
|
+
<div>
|
|
292
|
+
<h3>${translate('Manage filters')}</h3>
|
|
293
|
+
</div>
|
|
294
|
+
`
|
|
295
|
+
const body = Utils.loadTemplate(template)
|
|
296
|
+
this._listFilters(this._umap.filters, body, translate('Map (all layers)'))
|
|
297
|
+
this._umap.datalayers.active().forEach((datalayer) => {
|
|
298
|
+
this._listFilters(
|
|
299
|
+
datalayer.filters,
|
|
300
|
+
body,
|
|
301
|
+
`${datalayer.getName()} (${translate('single layer')})`
|
|
302
|
+
)
|
|
303
|
+
})
|
|
304
|
+
this._umap.dialog.open({ template: body })
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
_listFilters(filters, container, title) {
|
|
308
|
+
const template = `
|
|
309
|
+
<details open>
|
|
310
|
+
<summary>${title}</summary>
|
|
311
|
+
<ul data-ref=ul></ul>
|
|
312
|
+
<div>
|
|
313
|
+
<button type="button" data-ref=add>${translate('Add filter')}</button>
|
|
314
|
+
</div>
|
|
315
|
+
</details>
|
|
316
|
+
`
|
|
317
|
+
const [body, { ul, add }] = Utils.loadTemplateWithRefs(template)
|
|
318
|
+
if (!filters._parent.fields.size) {
|
|
319
|
+
add.disabled = true
|
|
320
|
+
ul.appendChild(
|
|
321
|
+
Utils.loadTemplate(
|
|
322
|
+
`<li>${translate('Add a field prior to create a filter.')}</li>`
|
|
323
|
+
)
|
|
324
|
+
)
|
|
325
|
+
}
|
|
326
|
+
if (!filters.size) {
|
|
327
|
+
body.open = false
|
|
328
|
+
}
|
|
329
|
+
filters.available.forEach((filter, fieldKey) => {
|
|
330
|
+
const [li, { edit, remove }] = Utils.loadTemplateWithRefs(
|
|
331
|
+
`<li class="orderable with-toolbox" data-fieldkey="${fieldKey}">
|
|
332
|
+
<span>
|
|
333
|
+
${filter.label || fieldKey}
|
|
334
|
+
</span>
|
|
335
|
+
<span>
|
|
336
|
+
<button class="icon icon-16 icon-edit" data-ref="edit" title="${translate('Edit this filter')}"></button>
|
|
337
|
+
<button class="icon icon-16 icon-delete" data-ref="remove" title="${translate('Remove this filter')}"></button>
|
|
338
|
+
<i class="icon icon-16 icon-drag" title="${translate('Drag to reorder')}"></i>
|
|
339
|
+
</span>
|
|
340
|
+
</li>`
|
|
341
|
+
)
|
|
342
|
+
ul.appendChild(li)
|
|
343
|
+
remove.addEventListener('click', () => {
|
|
344
|
+
filters.remove(fieldKey)
|
|
345
|
+
filters._parent
|
|
346
|
+
.edit()
|
|
347
|
+
.then((panel) => panel.scrollTo('details#fields-management'))
|
|
348
|
+
})
|
|
349
|
+
edit.addEventListener('click', () => {
|
|
350
|
+
filters.createFilterForm(fieldKey)
|
|
351
|
+
})
|
|
352
|
+
})
|
|
353
|
+
add.addEventListener('click', () => filters.createFilterForm())
|
|
354
|
+
const onReorder = (src, dst, initialIndex, finalIndex) => {
|
|
355
|
+
const orderedKeys = Array.from(ul.querySelectorAll('li')).map(
|
|
356
|
+
(el) => el.dataset.fieldkey
|
|
357
|
+
)
|
|
358
|
+
const oldValue = Utils.CopyJSON(filters._parent.properties.filters)
|
|
359
|
+
const copy = filters.available.entries().reduce((acc, [key, filter]) => {
|
|
360
|
+
acc[key] = filter.dumps()
|
|
361
|
+
return acc
|
|
362
|
+
}, {})
|
|
363
|
+
|
|
364
|
+
filters.available.clear()
|
|
365
|
+
for (const fieldKey of orderedKeys) {
|
|
366
|
+
filters.add({ ...copy[fieldKey] })
|
|
367
|
+
}
|
|
368
|
+
filters._parent.sync.update(
|
|
369
|
+
'properties.filters',
|
|
370
|
+
filters._parent.properties.filters,
|
|
371
|
+
oldValue
|
|
372
|
+
)
|
|
373
|
+
}
|
|
374
|
+
const orderable = new Orderable(ul, onReorder)
|
|
375
|
+
container.appendChild(body)
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
createFilterForm(fieldKey) {
|
|
379
|
+
let widget = 'Checkbox'
|
|
380
|
+
const field = this._parent.fields.get(fieldKey)
|
|
381
|
+
if (['Number', 'Date', 'Datetime'].includes(field?.type)) {
|
|
382
|
+
widget = 'MinMax'
|
|
383
|
+
} else if (field?.type === 'Boolean') {
|
|
384
|
+
widget = 'Switch'
|
|
385
|
+
}
|
|
386
|
+
const properties = {
|
|
387
|
+
target: this._parent.fields.size ? this._parent : null,
|
|
388
|
+
fieldKey,
|
|
389
|
+
widget,
|
|
390
|
+
...(this.available?.get(fieldKey)?.dumps() || {}),
|
|
391
|
+
}
|
|
392
|
+
const fieldKeys = fieldKey
|
|
393
|
+
? [fieldKey]
|
|
394
|
+
: [
|
|
395
|
+
'',
|
|
396
|
+
...this._parent.fieldKeys.filter((fieldKey) => !this.available.has(fieldKey)),
|
|
397
|
+
]
|
|
398
|
+
const metadata = [
|
|
399
|
+
[
|
|
400
|
+
'target',
|
|
401
|
+
{
|
|
402
|
+
handler: 'FilterTargetSelect',
|
|
403
|
+
label: translate('Apply filter to'),
|
|
404
|
+
disabled: Boolean(fieldKey),
|
|
405
|
+
},
|
|
406
|
+
],
|
|
407
|
+
[
|
|
408
|
+
'fieldKey',
|
|
409
|
+
{
|
|
410
|
+
handler: 'Select',
|
|
411
|
+
selectOptions: fieldKeys,
|
|
412
|
+
label: translate('Filter on'),
|
|
413
|
+
},
|
|
414
|
+
],
|
|
415
|
+
[
|
|
416
|
+
'label',
|
|
417
|
+
{ handler: 'Input', label: translate('Human readable name of the filter') },
|
|
418
|
+
],
|
|
419
|
+
[
|
|
420
|
+
'widget',
|
|
421
|
+
{
|
|
422
|
+
handler: 'MultiChoice',
|
|
423
|
+
choices: Object.entries(Widgets).map(([key, klass]) => [key, klass.NAME]),
|
|
424
|
+
label: translate('Widget for the filter'),
|
|
425
|
+
},
|
|
426
|
+
],
|
|
427
|
+
]
|
|
428
|
+
const form = new Form(properties, metadata, { umap: this._umap })
|
|
429
|
+
let label
|
|
430
|
+
if (fieldKey) {
|
|
431
|
+
label = translate('Edit filter')
|
|
432
|
+
} else {
|
|
433
|
+
label = translate('Add filter')
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
const [container, { body, editField }] = Utils.loadTemplateWithRefs(`
|
|
437
|
+
<div>
|
|
438
|
+
<h3>${label}</h3>
|
|
439
|
+
<div data-ref=body></div>
|
|
440
|
+
<button type="button" data-ref=editField><i class="icon icon-16 icon-edit"></i>${translate('Edit this field')}</button>
|
|
441
|
+
</div>
|
|
442
|
+
`)
|
|
443
|
+
body.appendChild(form.build())
|
|
444
|
+
editField.addEventListener('click', () => {
|
|
445
|
+
this._umap.dialog.accept()
|
|
446
|
+
this._parent.fields.editField(fieldKey)
|
|
447
|
+
})
|
|
448
|
+
|
|
449
|
+
return this._umap.dialog.open({ template: container }).then(() => {
|
|
450
|
+
const target = properties.target
|
|
451
|
+
if (!target) return
|
|
452
|
+
if (!properties.fieldKey) return
|
|
453
|
+
if (fieldKey) {
|
|
454
|
+
target.filters.update({ ...properties })
|
|
455
|
+
} else {
|
|
456
|
+
target.filters.add({ ...properties })
|
|
457
|
+
}
|
|
458
|
+
target.filters._parent
|
|
459
|
+
.edit()
|
|
460
|
+
.then((panel) => panel.scrollTo('details#fields-management'))
|
|
461
|
+
})
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
buildForm(container) {
|
|
465
|
+
const form = new FiltersForm(this, this.buildFormFields(), { className: 'formbox' })
|
|
466
|
+
container.appendChild(form.build())
|
|
467
|
+
return form
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
matchFeature(feature) {
|
|
471
|
+
for (const [fieldKey, obj] of this.available.entries()) {
|
|
472
|
+
if (!obj.isActive()) continue
|
|
473
|
+
const field = this._parent.fields.get(fieldKey)
|
|
474
|
+
// This field may only exist on another layer.
|
|
475
|
+
if (!field) continue
|
|
476
|
+
let value = feature.properties[fieldKey]
|
|
477
|
+
const parser = getParser(field.type)
|
|
478
|
+
value = parser(value)
|
|
479
|
+
if (obj.match(value)) return true
|
|
480
|
+
}
|
|
481
|
+
return false
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
class FiltersForm extends Form {
|
|
486
|
+
buildField(field) {
|
|
487
|
+
const [root, elements] = field.buildTemplate()
|
|
488
|
+
elements.editFilter.addEventListener('click', field.properties.onClick)
|
|
489
|
+
field.build()
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
getHelperTemplate(helper) {
|
|
493
|
+
return helper.getTemplate()
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
getTemplate(helper) {
|
|
497
|
+
return `
|
|
498
|
+
<fieldset class="umap-filter">
|
|
499
|
+
<legend data-ref=label>
|
|
500
|
+
<span>${helper.properties.label}</span>
|
|
501
|
+
<span class="filter-toolbox">
|
|
502
|
+
<button type="button" class="icon icon-16 icon-edit show-on-edit" data-ref=editFilter title="${translate('Edit filter')}"></button>
|
|
503
|
+
</span>
|
|
504
|
+
</legend>
|
|
505
|
+
<div data-ref="container">${helper.getTemplate()}</div>
|
|
506
|
+
</fieldset>
|
|
507
|
+
`
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
const FilterBase = class extends Fields.Base {
|
|
512
|
+
buildLabel() {}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
const FilterByChoices = class extends FilterBase {
|
|
516
|
+
getTemplate() {
|
|
517
|
+
return '<ul data-ref=ul></ul>'
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
build() {
|
|
521
|
+
this.type = this.getType()
|
|
522
|
+
|
|
523
|
+
const choices = Array.from(this.properties.initialData.choices || [])
|
|
524
|
+
choices.sort()
|
|
525
|
+
choices.forEach((value) => this.buildLi(value))
|
|
526
|
+
super.build()
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
buildLi(value) {
|
|
530
|
+
const name = `${this.type}_${this.name}`
|
|
531
|
+
const [li, { input, label }] = Utils.loadTemplateWithRefs(`
|
|
532
|
+
<li>
|
|
533
|
+
<label>
|
|
534
|
+
<input type="${this.type}" name="${name}" data-ref=input />
|
|
535
|
+
<span data-ref=label></span>
|
|
536
|
+
</label>
|
|
537
|
+
</li>
|
|
538
|
+
`)
|
|
539
|
+
label.textContent = value
|
|
540
|
+
input.checked = this.get()?.selected?.includes(value)
|
|
541
|
+
input.dataset.value = value
|
|
542
|
+
input.addEventListener('change', () => this.sync())
|
|
543
|
+
this.elements.ul.appendChild(li)
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
toJS() {
|
|
547
|
+
return {
|
|
548
|
+
selected: [...this.elements.ul.querySelectorAll('input:checked')].map(
|
|
549
|
+
(i) => i.dataset.value
|
|
550
|
+
),
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
Fields.FilterByCheckbox = class extends FilterByChoices {
|
|
556
|
+
getType() {
|
|
557
|
+
return 'checkbox'
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
Fields.FilterByRadio = class extends FilterByChoices {
|
|
562
|
+
getType() {
|
|
563
|
+
return 'radio'
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
Fields.MinMaxBase = class extends FilterBase {
|
|
568
|
+
getLabels() {
|
|
569
|
+
return [translate('Min'), translate('Max')]
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
prepareForHTML(value) {
|
|
573
|
+
return value?.valueOf() ?? null
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
getTemplate() {
|
|
577
|
+
const [minLabel, maxLabel] = this.getLabels()
|
|
578
|
+
const { min, max } = this.properties.initialData
|
|
579
|
+
const inputType = this.getInputType()
|
|
580
|
+
const minHTML = this.prepareForHTML(min)
|
|
581
|
+
const maxHTML = this.prepareForHTML(max)
|
|
582
|
+
return `
|
|
583
|
+
<label>${minLabel}<input min="${minHTML}" max="${maxHTML}" step=any type="${inputType}" data-ref=minInput /></label>
|
|
584
|
+
<label>${maxLabel}<input min="${minHTML}" max="${maxHTML}" step=any type="${inputType}" data-ref=maxInput /></label>
|
|
585
|
+
`
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
build() {
|
|
589
|
+
this.minInput = this.elements.minInput
|
|
590
|
+
this.maxInput = this.elements.maxInput
|
|
591
|
+
const { min, max, type } = this.properties.initialData
|
|
592
|
+
const { min: userMin, max: userMax } = this.get() || {}
|
|
593
|
+
|
|
594
|
+
const currentMin = userMin !== undefined ? userMin : min
|
|
595
|
+
const currentMax = userMax !== undefined ? userMax : max
|
|
596
|
+
if (min != null) {
|
|
597
|
+
// The value stored using setAttribute is not modified by
|
|
598
|
+
// user input, and will be used as initial value when calling
|
|
599
|
+
// form.reset(), and can also be retrieve later on by using
|
|
600
|
+
// getAttributing, to compare with current value and know
|
|
601
|
+
// if this value has been modified by the user
|
|
602
|
+
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/reset
|
|
603
|
+
this.minInput.setAttribute('value', this.prepareForHTML(min))
|
|
604
|
+
this.minInput.value = this.prepareForHTML(currentMin)
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
if (max != null) {
|
|
608
|
+
// Cf comment above about setAttribute vs value
|
|
609
|
+
this.maxInput.setAttribute('value', this.prepareForHTML(max))
|
|
610
|
+
this.maxInput.value = this.prepareForHTML(currentMax)
|
|
611
|
+
}
|
|
612
|
+
this.toggleStatus()
|
|
613
|
+
|
|
614
|
+
this.minInput.addEventListener('change', () => this.sync())
|
|
615
|
+
this.maxInput.addEventListener('change', () => this.sync())
|
|
616
|
+
super.build()
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
toggleStatus() {
|
|
620
|
+
this.minInput.dataset.modified = this.isMinModified()
|
|
621
|
+
this.maxInput.dataset.modified = this.isMaxModified()
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
sync() {
|
|
625
|
+
super.sync()
|
|
626
|
+
this.toggleStatus()
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
isMinModified() {
|
|
630
|
+
const default_ = this.minInput.getAttribute('value')
|
|
631
|
+
const current = this.minInput.value
|
|
632
|
+
return current !== default_
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
isMaxModified() {
|
|
636
|
+
const default_ = this.maxInput.getAttribute('value')
|
|
637
|
+
const current = this.maxInput.value
|
|
638
|
+
return current !== default_
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
toJS() {
|
|
642
|
+
const opts = {}
|
|
643
|
+
if (this.minInput.value !== '' && this.isMinModified()) {
|
|
644
|
+
opts.min = this.prepareForJS(this.minInput.value)
|
|
645
|
+
}
|
|
646
|
+
if (this.maxInput.value !== '' && this.isMaxModified()) {
|
|
647
|
+
opts.max = this.prepareForJS(this.maxInput.value)
|
|
648
|
+
}
|
|
649
|
+
return opts
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
Fields.FilterByNumber = class extends Fields.MinMaxBase {
|
|
654
|
+
getInputType(type) {
|
|
655
|
+
return 'number'
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
prepareForJS(value) {
|
|
659
|
+
return new Number(value)
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
Fields.FilterByDate = class extends Fields.MinMaxBase {
|
|
664
|
+
getInputType(type) {
|
|
665
|
+
return 'date'
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
prepareForJS(value) {
|
|
669
|
+
return new Date(value)
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
toLocaleDateTime(dt) {
|
|
673
|
+
return new Date(dt.valueOf() - dt.getTimezoneOffset() * 60000)
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
prepareForHTML(value) {
|
|
677
|
+
// Value must be in local time
|
|
678
|
+
if (!value || isNaN(value)) return
|
|
679
|
+
return this.toLocaleDateTime(value).toISOString().substr(0, 10)
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
getLabels() {
|
|
683
|
+
return [translate('From'), translate('Until')]
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
Fields.FilterByDateTime = class extends Fields.FilterByDate {
|
|
688
|
+
getInputType() {
|
|
689
|
+
return 'datetime-local'
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
prepareForHTML(value) {
|
|
693
|
+
// Value must be in local time
|
|
694
|
+
if (Number.isNaN(value)) return
|
|
695
|
+
return this.toLocaleDateTime(value).toISOString().slice(0, -1)
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
Fields.FilterTargetSelect = class extends Fields.Select {
|
|
700
|
+
getOptions() {
|
|
701
|
+
const options = []
|
|
702
|
+
if (this.builder.properties.umap.fields.size) {
|
|
703
|
+
if (!this.obj.target) {
|
|
704
|
+
this.obj.target = this.builder.properties.umap
|
|
705
|
+
}
|
|
706
|
+
options.push([
|
|
707
|
+
`map:${this.builder.properties.umap.id}`,
|
|
708
|
+
`${this.builder.properties.umap.properties.name} (${translate('all layers')})`,
|
|
709
|
+
])
|
|
710
|
+
}
|
|
711
|
+
this.builder.properties.umap.datalayers.reverse().map((datalayer) => {
|
|
712
|
+
if (datalayer.isBrowsable() && datalayer.fields.size) {
|
|
713
|
+
if (!this.obj.target) {
|
|
714
|
+
this.obj.target = datalayer
|
|
715
|
+
}
|
|
716
|
+
options.push([
|
|
717
|
+
`layer:${datalayer.id}`,
|
|
718
|
+
`${datalayer.getName()} (${translate('single layer')})`,
|
|
719
|
+
])
|
|
720
|
+
}
|
|
721
|
+
})
|
|
722
|
+
return options
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
toHTML() {
|
|
726
|
+
if (!this.obj.target) return null
|
|
727
|
+
// TODO: better way to check for class
|
|
728
|
+
// Importing DataLayer will end in circular import
|
|
729
|
+
const type = this.obj.target._umap ? 'layer' : 'map'
|
|
730
|
+
return `${type}:${this.obj.target?.id}`
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
toJS() {
|
|
734
|
+
const value = this.value()
|
|
735
|
+
if (!value) return null
|
|
736
|
+
const [type, id] = value.split(':')
|
|
737
|
+
if (type === 'map') {
|
|
738
|
+
return this.builder.properties.umap
|
|
739
|
+
}
|
|
740
|
+
return this.builder.properties.umap.datalayers[id]
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
Fields.FilterBySwitch = class extends FilterBase {
|
|
745
|
+
getTemplate() {
|
|
746
|
+
return `
|
|
747
|
+
<div class="ternary-switch">
|
|
748
|
+
<input type="radio" id="${this.id}.1" name="${this.name}" value="true" />
|
|
749
|
+
<label tabindex="0" for="${this.id}.1">${translate('yes')}</label>
|
|
750
|
+
<input type="radio" id="${this.id}.2" name="${this.name}" value="unset" checked />
|
|
751
|
+
<label tabindex="0" for="${this.id}.2">${translate('unset')}</label>
|
|
752
|
+
<input type="radio" id="${this.id}.3" name="${this.name}" value="false" />
|
|
753
|
+
<label tabindex="0" for="${this.id}.3">${translate('no')}</label>
|
|
754
|
+
</div>
|
|
755
|
+
`
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
build() {
|
|
759
|
+
super.build()
|
|
760
|
+
this.inputs = Array.from(this.form[this.name])
|
|
761
|
+
for (const input of this.inputs) {
|
|
762
|
+
input.addEventListener('change', () => this.sync())
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
value() {
|
|
767
|
+
return this.form[this.name].value
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
toJS() {
|
|
771
|
+
if (this.value() === 'unset') return {}
|
|
772
|
+
return { wanted: this.value() === 'true' }
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
export const migrateLegacyFilters = (properties) => {
|
|
777
|
+
const legacy = properties.advancedFilterKey || properties.facetKey
|
|
778
|
+
if (!legacy) return false
|
|
779
|
+
properties.filters ??= []
|
|
780
|
+
properties.fields ??= []
|
|
781
|
+
for (const filter of legacy.split(',')) {
|
|
782
|
+
let [fieldKey, label, widget] = filter.split('|')
|
|
783
|
+
let type = 'String'
|
|
784
|
+
if (['number', 'date', 'datetime'].includes(widget)) {
|
|
785
|
+
// Retrocompat
|
|
786
|
+
if (widget === 'number') {
|
|
787
|
+
type = 'Number'
|
|
788
|
+
} else if (widget === 'datetime') {
|
|
789
|
+
type = 'Datetime'
|
|
790
|
+
} else if (widget === 'date') {
|
|
791
|
+
type = 'Date'
|
|
792
|
+
}
|
|
793
|
+
widget = 'MinMax'
|
|
794
|
+
}
|
|
795
|
+
if (widget === 'radio') {
|
|
796
|
+
widget = 'Radio'
|
|
797
|
+
}
|
|
798
|
+
if (!(widget in Widgets)) {
|
|
799
|
+
widget = 'Checkbox'
|
|
800
|
+
}
|
|
801
|
+
properties.filters.push({ fieldKey, label, widget })
|
|
802
|
+
properties.fields.push({ key: fieldKey, type })
|
|
803
|
+
}
|
|
804
|
+
delete properties.facetKey
|
|
805
|
+
delete properties.advancedFilterKey
|
|
806
|
+
return true
|
|
807
|
+
}
|