umap-project 3.3.6__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.
Files changed (239) hide show
  1. umap/__init__.py +1 -1
  2. umap/context_processors.py +4 -1
  3. umap/locale/cs_CZ/LC_MESSAGES/django.mo +0 -0
  4. umap/locale/cs_CZ/LC_MESSAGES/django.po +43 -33
  5. umap/locale/da/LC_MESSAGES/django.mo +0 -0
  6. umap/locale/da/LC_MESSAGES/django.po +43 -33
  7. umap/locale/de/LC_MESSAGES/django.mo +0 -0
  8. umap/locale/de/LC_MESSAGES/django.po +35 -29
  9. umap/locale/el/LC_MESSAGES/django.mo +0 -0
  10. umap/locale/el/LC_MESSAGES/django.po +35 -29
  11. umap/locale/en/LC_MESSAGES/django.po +47 -41
  12. umap/locale/es/LC_MESSAGES/django.mo +0 -0
  13. umap/locale/es/LC_MESSAGES/django.po +43 -33
  14. umap/locale/et/LC_MESSAGES/django.mo +0 -0
  15. umap/locale/et/LC_MESSAGES/django.po +58 -54
  16. umap/locale/eu/LC_MESSAGES/django.mo +0 -0
  17. umap/locale/eu/LC_MESSAGES/django.po +43 -33
  18. umap/locale/fa_IR/LC_MESSAGES/django.mo +0 -0
  19. umap/locale/fa_IR/LC_MESSAGES/django.po +43 -33
  20. umap/locale/fr/LC_MESSAGES/django.mo +0 -0
  21. umap/locale/fr/LC_MESSAGES/django.po +36 -30
  22. umap/locale/gl/LC_MESSAGES/django.mo +0 -0
  23. umap/locale/gl/LC_MESSAGES/django.po +43 -33
  24. umap/locale/hu/LC_MESSAGES/django.mo +0 -0
  25. umap/locale/hu/LC_MESSAGES/django.po +35 -29
  26. umap/locale/is/LC_MESSAGES/django.mo +0 -0
  27. umap/locale/is/LC_MESSAGES/django.po +43 -33
  28. umap/locale/it/LC_MESSAGES/django.mo +0 -0
  29. umap/locale/it/LC_MESSAGES/django.po +43 -33
  30. umap/locale/nl/LC_MESSAGES/django.mo +0 -0
  31. umap/locale/nl/LC_MESSAGES/django.po +35 -29
  32. umap/locale/pl/LC_MESSAGES/django.mo +0 -0
  33. umap/locale/pl/LC_MESSAGES/django.po +114 -103
  34. umap/locale/pt/LC_MESSAGES/django.mo +0 -0
  35. umap/locale/pt/LC_MESSAGES/django.po +43 -33
  36. umap/locale/th_TH/LC_MESSAGES/django.mo +0 -0
  37. umap/locale/th_TH/LC_MESSAGES/django.po +310 -109
  38. umap/locale/zh_TW/LC_MESSAGES/django.mo +0 -0
  39. umap/locale/zh_TW/LC_MESSAGES/django.po +80 -70
  40. umap/management/commands/switch_user.py +2 -2
  41. umap/migrations/0018_datalayer_uuid.py +1 -1
  42. umap/models.py +7 -3
  43. umap/settings/local.py.sample +1 -1
  44. umap/static/umap/base.css +89 -32
  45. umap/static/umap/content.css +129 -33
  46. umap/static/umap/css/bar.css +82 -20
  47. umap/static/umap/css/browser.css +163 -0
  48. umap/static/umap/css/contextmenu.css +15 -0
  49. umap/static/umap/css/dialog.css +36 -16
  50. umap/static/umap/css/form.css +123 -33
  51. umap/static/umap/css/icon.css +46 -3
  52. umap/static/umap/css/panel.css +7 -3
  53. umap/static/umap/css/popup.css +34 -8
  54. umap/static/umap/css/tooltip.css +8 -4
  55. umap/static/umap/img/16-white.svg +26 -8
  56. umap/static/umap/img/16.svg +1 -1
  57. umap/static/umap/img/source/16-white.svg +36 -18
  58. umap/static/umap/img/source/16.svg +1 -1
  59. umap/static/umap/js/components/alerts/alert.css +69 -31
  60. umap/static/umap/js/components/alerts/alert.js +20 -2
  61. umap/static/umap/js/components/base.js +1 -1
  62. umap/static/umap/js/modules/browser.js +69 -61
  63. umap/static/umap/js/modules/caption.js +10 -7
  64. umap/static/umap/js/modules/data/features.js +85 -60
  65. umap/static/umap/js/modules/data/fields.js +446 -0
  66. umap/static/umap/js/modules/data/layer.js +78 -184
  67. umap/static/umap/js/modules/domutils.js +109 -0
  68. umap/static/umap/js/modules/filters.js +780 -0
  69. umap/static/umap/js/modules/form/builder.js +8 -5
  70. umap/static/umap/js/modules/form/fields.js +111 -221
  71. umap/static/umap/js/modules/formatter.js +24 -1
  72. umap/static/umap/js/modules/help.js +4 -3
  73. umap/static/umap/js/modules/i18n.js +1 -1
  74. umap/static/umap/js/modules/importer.js +1 -1
  75. umap/static/umap/js/modules/importers/opendata.js +15 -0
  76. umap/static/umap/js/modules/importers/openrouteservice.js +6 -1
  77. umap/static/umap/js/modules/managers.js +2 -2
  78. umap/static/umap/js/modules/permissions.js +39 -31
  79. umap/static/umap/js/modules/rendering/controls.js +11 -9
  80. umap/static/umap/js/modules/rendering/icon.js +3 -8
  81. umap/static/umap/js/modules/rendering/layers/base.js +1 -1
  82. umap/static/umap/js/modules/rendering/layers/classified.js +18 -11
  83. umap/static/umap/js/modules/rendering/layers/cluster.js +5 -3
  84. umap/static/umap/js/modules/rendering/layers/heat.js +27 -21
  85. umap/static/umap/js/modules/rendering/template.js +50 -23
  86. umap/static/umap/js/modules/rendering/ui.js +29 -23
  87. umap/static/umap/js/modules/rules.js +38 -44
  88. umap/static/umap/js/modules/schema.js +3 -6
  89. umap/static/umap/js/modules/share.js +5 -4
  90. umap/static/umap/js/modules/tableeditor.js +50 -38
  91. umap/static/umap/js/modules/templates.js +2 -3
  92. umap/static/umap/js/modules/ui/bar.js +55 -23
  93. umap/static/umap/js/modules/ui/dialog.js +38 -27
  94. umap/static/umap/js/modules/ui/panel.js +23 -8
  95. umap/static/umap/js/modules/ui/tooltip.js +6 -5
  96. umap/static/umap/js/modules/umap.js +151 -56
  97. umap/static/umap/js/modules/utils.js +24 -2
  98. umap/static/umap/js/umap.core.js +1 -110
  99. umap/static/umap/locale/am_ET.js +52 -17
  100. umap/static/umap/locale/am_ET.json +52 -17
  101. umap/static/umap/locale/ar.js +52 -17
  102. umap/static/umap/locale/ar.json +52 -17
  103. umap/static/umap/locale/ast.js +52 -17
  104. umap/static/umap/locale/ast.json +52 -17
  105. umap/static/umap/locale/bg.js +52 -17
  106. umap/static/umap/locale/bg.json +52 -17
  107. umap/static/umap/locale/br.js +48 -22
  108. umap/static/umap/locale/br.json +48 -22
  109. umap/static/umap/locale/ca.js +52 -17
  110. umap/static/umap/locale/ca.json +52 -17
  111. umap/static/umap/locale/cs_CZ.js +52 -17
  112. umap/static/umap/locale/cs_CZ.json +52 -17
  113. umap/static/umap/locale/da.js +54 -17
  114. umap/static/umap/locale/da.json +54 -17
  115. umap/static/umap/locale/de.js +51 -16
  116. umap/static/umap/locale/de.json +51 -16
  117. umap/static/umap/locale/el.js +52 -17
  118. umap/static/umap/locale/el.json +52 -17
  119. umap/static/umap/locale/en.js +53 -16
  120. umap/static/umap/locale/en.json +53 -16
  121. umap/static/umap/locale/en_US.json +52 -17
  122. umap/static/umap/locale/es.js +54 -17
  123. umap/static/umap/locale/es.json +54 -17
  124. umap/static/umap/locale/et.js +91 -56
  125. umap/static/umap/locale/et.json +91 -56
  126. umap/static/umap/locale/eu.js +84 -49
  127. umap/static/umap/locale/eu.json +84 -49
  128. umap/static/umap/locale/fa_IR.js +52 -17
  129. umap/static/umap/locale/fa_IR.json +52 -17
  130. umap/static/umap/locale/fi.js +52 -17
  131. umap/static/umap/locale/fi.json +52 -17
  132. umap/static/umap/locale/fr.js +53 -16
  133. umap/static/umap/locale/fr.json +53 -16
  134. umap/static/umap/locale/gl.js +52 -17
  135. umap/static/umap/locale/gl.json +52 -17
  136. umap/static/umap/locale/he.js +52 -17
  137. umap/static/umap/locale/he.json +52 -17
  138. umap/static/umap/locale/hr.js +52 -17
  139. umap/static/umap/locale/hr.json +52 -17
  140. umap/static/umap/locale/hu.js +59 -24
  141. umap/static/umap/locale/hu.json +59 -24
  142. umap/static/umap/locale/id.js +52 -17
  143. umap/static/umap/locale/id.json +52 -17
  144. umap/static/umap/locale/is.js +52 -17
  145. umap/static/umap/locale/is.json +52 -17
  146. umap/static/umap/locale/it.js +52 -17
  147. umap/static/umap/locale/it.json +52 -17
  148. umap/static/umap/locale/ja.js +52 -17
  149. umap/static/umap/locale/ja.json +52 -17
  150. umap/static/umap/locale/ko.js +52 -17
  151. umap/static/umap/locale/ko.json +52 -17
  152. umap/static/umap/locale/lt.js +52 -17
  153. umap/static/umap/locale/lt.json +52 -17
  154. umap/static/umap/locale/ms.js +52 -17
  155. umap/static/umap/locale/ms.json +52 -17
  156. umap/static/umap/locale/nl.js +52 -17
  157. umap/static/umap/locale/nl.json +52 -17
  158. umap/static/umap/locale/no.js +52 -17
  159. umap/static/umap/locale/no.json +52 -17
  160. umap/static/umap/locale/pl.js +53 -17
  161. umap/static/umap/locale/pl.json +53 -17
  162. umap/static/umap/locale/pl_PL.json +52 -17
  163. umap/static/umap/locale/pt.js +52 -17
  164. umap/static/umap/locale/pt.json +52 -17
  165. umap/static/umap/locale/pt_BR.js +52 -17
  166. umap/static/umap/locale/pt_BR.json +52 -17
  167. umap/static/umap/locale/pt_PT.js +52 -17
  168. umap/static/umap/locale/pt_PT.json +52 -17
  169. umap/static/umap/locale/ro.js +52 -17
  170. umap/static/umap/locale/ro.json +52 -17
  171. umap/static/umap/locale/ru.js +52 -17
  172. umap/static/umap/locale/ru.json +52 -17
  173. umap/static/umap/locale/si.js +1 -1
  174. umap/static/umap/locale/si.json +1 -1
  175. umap/static/umap/locale/sk_SK.js +52 -17
  176. umap/static/umap/locale/sk_SK.json +52 -17
  177. umap/static/umap/locale/sl.js +52 -17
  178. umap/static/umap/locale/sl.json +52 -17
  179. umap/static/umap/locale/sr.js +52 -17
  180. umap/static/umap/locale/sr.json +52 -17
  181. umap/static/umap/locale/sv.js +52 -17
  182. umap/static/umap/locale/sv.json +52 -17
  183. umap/static/umap/locale/th_TH.js +52 -17
  184. umap/static/umap/locale/th_TH.json +52 -17
  185. umap/static/umap/locale/tr.js +52 -17
  186. umap/static/umap/locale/tr.json +52 -17
  187. umap/static/umap/locale/uk_UA.js +52 -17
  188. umap/static/umap/locale/uk_UA.json +52 -17
  189. umap/static/umap/locale/vi.js +52 -17
  190. umap/static/umap/locale/vi.json +52 -17
  191. umap/static/umap/locale/vi_VN.json +52 -17
  192. umap/static/umap/locale/zh.js +52 -17
  193. umap/static/umap/locale/zh.json +52 -17
  194. umap/static/umap/locale/zh_CN.json +52 -17
  195. umap/static/umap/locale/zh_TW.Big5.json +52 -17
  196. umap/static/umap/locale/zh_TW.js +52 -16
  197. umap/static/umap/locale/zh_TW.json +52 -16
  198. umap/static/umap/map.css +63 -226
  199. umap/static/umap/unittests/utils.js +18 -0
  200. umap/static/umap/vars.css +23 -5
  201. umap/templates/umap/components/alerts/alert.html +32 -29
  202. umap/templates/umap/css.html +2 -1
  203. umap/templates/umap/login_popup_end.html +18 -9
  204. umap/templates/umap/user_map_table.html +7 -2
  205. umap/tests/integration/conftest.py +10 -6
  206. umap/tests/integration/test_anonymous_owned_map.py +90 -37
  207. umap/tests/integration/test_basics.py +25 -1
  208. umap/tests/integration/test_browser.py +37 -0
  209. umap/tests/integration/test_conditional_rules.py +107 -52
  210. umap/tests/integration/test_draw_polygon.py +6 -0
  211. umap/tests/integration/test_draw_polyline.py +11 -0
  212. umap/tests/integration/test_edit_marker.py +1 -1
  213. umap/tests/integration/test_export_map.py +19 -0
  214. umap/tests/integration/test_fields.py +541 -0
  215. umap/tests/integration/test_filters.py +616 -0
  216. umap/tests/integration/test_iframe.py +1 -1
  217. umap/tests/integration/test_import.py +38 -42
  218. umap/tests/integration/test_map_preview.py +1 -1
  219. umap/tests/integration/test_picto.py +1 -1
  220. umap/tests/integration/test_popup.py +31 -0
  221. umap/tests/integration/test_remote_data.py +60 -4
  222. umap/tests/integration/test_save.py +1 -1
  223. umap/tests/integration/test_share.py +4 -4
  224. umap/tests/integration/test_tableeditor.py +31 -7
  225. umap/tests/integration/test_websocket_sync.py +71 -20
  226. umap/tests/test_dashboard.py +11 -1
  227. umap/tests/test_statics.py +2 -2
  228. umap/tests/test_utils.py +19 -2
  229. umap/tests/test_views.py +1 -1
  230. umap/urls.py +1 -0
  231. umap/utils.py +8 -1
  232. umap/views.py +5 -0
  233. {umap_project-3.3.6.dist-info → umap_project-3.4.0.dist-info}/METADATA +15 -15
  234. {umap_project-3.3.6.dist-info → umap_project-3.4.0.dist-info}/RECORD +237 -233
  235. umap/static/umap/js/modules/facets.js +0 -164
  236. umap/tests/integration/test_facets_browser.py +0 -279
  237. {umap_project-3.3.6.dist-info → umap_project-3.4.0.dist-info}/WHEEL +0 -0
  238. {umap_project-3.3.6.dist-info → umap_project-3.4.0.dist-info}/entry_points.txt +0 -0
  239. {umap_project-3.3.6.dist-info → umap_project-3.4.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,446 @@
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 const Registry = {}
13
+
14
+ class BaseField {
15
+ constructor(key) {
16
+ this.key = key
17
+ }
18
+
19
+ values(features) {
20
+ return features
21
+ .map((feature) => this.parse(feature.properties[this.key]))
22
+ .filter((val, idx, arr) => arr.indexOf(val) === idx)
23
+ }
24
+
25
+ equal(expected, other) {
26
+ return expected === other
27
+ }
28
+
29
+ not_equal(expected, other) {
30
+ return expected !== other
31
+ }
32
+
33
+ gt(expected, other) {
34
+ return other > expected
35
+ }
36
+
37
+ lt(expected, other) {
38
+ return other < expected
39
+ }
40
+
41
+ render(value) {
42
+ return Utils.escapeHTML(value).trim()
43
+ }
44
+
45
+ dumps() {
46
+ return {
47
+ key: this.key,
48
+ type: this.TYPE,
49
+ }
50
+ }
51
+ }
52
+
53
+ Registry.String = class extends BaseField {
54
+ constructor(key) {
55
+ super(key)
56
+ // FIXME make it dynamic from class name
57
+ this.TYPE = 'String'
58
+ this.LABEL = translate('Short text')
59
+ }
60
+
61
+ parse(value) {
62
+ return String(value ?? '')
63
+ }
64
+
65
+ render(value) {
66
+ value = super.render(value)
67
+ // TODO, manage links (url, mailto, wikipedia...)
68
+ if (value.indexOf('http') === 0) {
69
+ value = `<a href="${value}" target="_blank">${value}</a>`
70
+ }
71
+ return value
72
+ }
73
+ }
74
+
75
+ Registry.Text = class extends Registry.String {
76
+ constructor(key) {
77
+ super(key)
78
+ // FIXME make it dynamic from class name
79
+ this.TYPE = 'Text'
80
+ this.LABEL = translate('Text')
81
+ }
82
+
83
+ render(value) {
84
+ return Utils.toHTML(value)
85
+ }
86
+ }
87
+
88
+ Registry.Number = class extends BaseField {
89
+ constructor(key) {
90
+ super(key)
91
+ // FIXME make it dynamic from class name
92
+ this.TYPE = 'Number'
93
+ this.LABEL = translate('Number')
94
+ }
95
+
96
+ parse(value) {
97
+ return Number.parseFloat(value)
98
+ }
99
+ }
100
+
101
+ Registry.Datetime = class extends BaseField {
102
+ constructor(key) {
103
+ super(key)
104
+ // FIXME make it dynamic from class name
105
+ this.TYPE = 'Datetime'
106
+ this.LABEL = translate('Date and time')
107
+ }
108
+
109
+ parse(value) {
110
+ return new Date(value)
111
+ }
112
+ }
113
+
114
+ Registry.Date = class extends BaseField {
115
+ constructor(key) {
116
+ super(key)
117
+ // FIXME make it dynamic from class name
118
+ this.TYPE = 'Date'
119
+ this.LABEL = translate('Date')
120
+ }
121
+
122
+ parse(value) {
123
+ return Utils.parseNaiveDate(value)
124
+ }
125
+ }
126
+
127
+ Registry.Boolean = class extends BaseField {
128
+ constructor(key) {
129
+ super(key)
130
+ // FIXME make it dynamic from class name
131
+ this.TYPE = 'Boolean'
132
+ this.LABEL = translate('Yes / No')
133
+ }
134
+
135
+ parse(value) {
136
+ // 'yes' is used in OpenStreetMap data
137
+ return ['true', '1', 'yes'].includes(`${value}`.toLowerCase())
138
+ }
139
+ }
140
+
141
+ Registry.Enum = class extends BaseField {
142
+ constructor(key) {
143
+ super(key)
144
+ // FIXME make it dynamic from class name
145
+ this.TYPE = 'Enum'
146
+ this.LABEL = translate('List of values')
147
+ }
148
+
149
+ parse(value) {
150
+ return String(value || '')
151
+ .split(',')
152
+ .map((s) => s.trim())
153
+ }
154
+
155
+ values(features) {
156
+ return features
157
+ .reduce(
158
+ (acc, feature) => acc.concat(this.parse(feature.properties[this.key])),
159
+ []
160
+ )
161
+ .filter((val, idx, arr) => arr.indexOf(val) === idx)
162
+ }
163
+
164
+ equal(expected, other) {
165
+ return Boolean(new Set(other).intersection(new Set(expected)).size)
166
+ }
167
+
168
+ not_equal(expected, other) {
169
+ return !new Set(other).intersection(new Set(expected)).size
170
+ }
171
+
172
+ gt(expected, other) {
173
+ return false
174
+ }
175
+
176
+ lt(expected, other) {
177
+ return false
178
+ }
179
+ }
180
+
181
+ const FIELD_TYPES = Object.entries(Registry).map(([name, klass]) => [
182
+ name,
183
+ new klass().LABEL,
184
+ ])
185
+
186
+ export class Fields extends Map {
187
+ constructor(parent, dialog) {
188
+ super()
189
+ this.parent = parent
190
+ this.dialog = dialog
191
+ this.parent.properties.fields ??= []
192
+ this.pull()
193
+ }
194
+
195
+ isDefault() {
196
+ const keys = Array.from(this.keys())
197
+ const defaultKeys = getDefaultFields().map((field) => field.key)
198
+ return (
199
+ keys.length === defaultKeys.length &&
200
+ keys.every((value, index) => value === defaultKeys[index])
201
+ )
202
+ }
203
+
204
+ pull() {
205
+ this.clear()
206
+ for (const field of this.parent.properties.fields) {
207
+ this.add(field)
208
+ }
209
+ }
210
+
211
+ push() {
212
+ this.parent.properties.fields = this.all().map((field) => {
213
+ // We don't want to keep the reference, otherwise editing
214
+ // it will also change the old value
215
+ return { ...field.dumps() }
216
+ })
217
+ }
218
+
219
+ async commit() {
220
+ return new Promise((resolve) => {
221
+ const oldFields = Utils.CopyJSON(this.parent.properties.fields)
222
+ resolve()
223
+ this.push()
224
+ this.parent.sync.update(
225
+ 'properties.fields',
226
+ this.parent.properties.fields,
227
+ oldFields
228
+ )
229
+ })
230
+ }
231
+
232
+ add(field) {
233
+ if (!field?.key) {
234
+ console.error('Invalid field', field)
235
+ return
236
+ }
237
+ const klass = Registry[field.type] || Registry.String
238
+ this.set(field.key, new klass(field.key))
239
+ this.push()
240
+ }
241
+
242
+ delete(key) {
243
+ super.delete(key)
244
+ this.push()
245
+ }
246
+
247
+ all() {
248
+ return Array.from(this.values())
249
+ }
250
+
251
+ edit(container) {
252
+ const [root, { ul, add, manageFilters }] = Utils.loadTemplateWithRefs(`
253
+ <details id="fields-management">
254
+ <summary><h4>${translate('Manage Fields')}</h4></summary>
255
+ <fieldset>
256
+ <ul data-ref=ul></ul>
257
+ <div class="button-bar half">
258
+ <button type="button" data-ref=add>${translate('Add a new field')}</button>
259
+ <button type="button" data-ref="manageFilters">${translate('Manage filters')}</button>
260
+ </div>
261
+ </fieldset>
262
+ </details>
263
+ `)
264
+ container.appendChild(root)
265
+ add.hidden = this.parent.isRemoteLayer?.()
266
+ add.addEventListener('click', () => {
267
+ this.editField().then(() => {
268
+ this.parent.edit().then((panel) => {
269
+ panel.scrollTo('details#fields-management')
270
+ })
271
+ })
272
+ })
273
+ manageFilters.addEventListener('click', () => this.parent.filters.edit())
274
+ for (const field of this.all()) {
275
+ const [row, { edit, del, addFilter, editFilter }] = Utils.loadTemplateWithRefs(
276
+ `<li class="orderable with-toolbox" data-key="${field.key}">
277
+ <span>
278
+ <i class="icon icon-16 icon-field-${field.TYPE}" title="${field.LABEL}"></i>
279
+ ${field.key}
280
+ </span>
281
+ <span>
282
+ <button class="icon icon-16 icon-edit" title="${translate('Edit this field')}" data-ref=edit></button>
283
+ <button class="icon icon-16 icon-filters" title="${translate('Edit filter')}" data-ref=editFilter></button>
284
+ <button class="icon icon-16 icon-filters-empty" title="${translate('Add a filter for this field')}" data-ref=addFilter></button>
285
+ <button class="icon icon-16 icon-delete" title="${translate('Delete this field')}" data-ref=del></button>
286
+ <i class="icon icon-16 icon-drag" title="${translate('Drag to reorder')}"></i>
287
+ </span>
288
+ </li>`
289
+ )
290
+ editFilter.hidden = !this.parent.filters.has(field.key)
291
+ addFilter.hidden = this.parent.filters.has(field.key)
292
+ del.hidden = this.parent.isRemoteLayer?.()
293
+ editFilter.addEventListener('click', () =>
294
+ this.parent.filters.createFilterForm(field.key)
295
+ )
296
+ addFilter.addEventListener('click', () =>
297
+ this.parent.filters.createFilterForm(field.key)
298
+ )
299
+ ul.appendChild(row)
300
+ edit.addEventListener('click', () => {
301
+ this.editField(field.key).then(() => {
302
+ this.parent.edit().then((panel) => {
303
+ panel.scrollTo('details#fields-management')
304
+ })
305
+ })
306
+ })
307
+ del.addEventListener('click', () => {
308
+ this.confirmDelete(field.key).then(() => {
309
+ this.parent.edit().then((panel) => {
310
+ panel.scrollTo('details#fields-management')
311
+ })
312
+ })
313
+ })
314
+ }
315
+ const onReorder = (src, dst, initialIndex, finalIndex) => {
316
+ const orderedKeys = Array.from(ul.querySelectorAll('li')).map(
317
+ (el) => el.dataset.key
318
+ )
319
+ const oldFields = Utils.CopyJSON(this.parent.properties.fields)
320
+ const copy = Object.fromEntries(this)
321
+ this.clear()
322
+ for (const key of orderedKeys) {
323
+ this.add(copy[key])
324
+ }
325
+ this.parent.sync.update(
326
+ 'properties.fields',
327
+ this.parent.properties.fields,
328
+ oldFields
329
+ )
330
+ }
331
+ const orderable = new Orderable(ul, onReorder)
332
+ }
333
+
334
+ async editField(name) {
335
+ if (!name && this.parent.isRemoteLayer?.()) return
336
+ const field = this.get(name)
337
+ const data = field?.dumps() || {}
338
+ const metadatas = [
339
+ [
340
+ 'key',
341
+ {
342
+ handler: 'BlurInput',
343
+ label: translate('Field Name'),
344
+ disabled: this.parent.isRemoteLayer?.(),
345
+ },
346
+ ],
347
+ [
348
+ 'type',
349
+ {
350
+ handler: 'Select',
351
+ selectOptions: FIELD_TYPES,
352
+ label: translate('Field Type'),
353
+ },
354
+ ],
355
+ ]
356
+ const form = new Form(data, metadatas)
357
+
358
+ const [container, { body, addFilter }] = Utils.loadTemplateWithRefs(`
359
+ <div>
360
+ <h3>${translate('Manage field')}</h3>
361
+ <div data-ref=body></div>
362
+ <button type="button" data-ref=addFilter hidden><i class="icon icon-16 icon-filters"></i>${translate('Add filter for this field')}</button>
363
+ </div>
364
+ `)
365
+ body.appendChild(form.build())
366
+ if (this.parent.filters) {
367
+ addFilter.addEventListener('click', () => {
368
+ this.dialog.accept().then(() => {
369
+ this.parent.filters.createFilterForm(data.key)
370
+ })
371
+ })
372
+ addFilter.hidden = false
373
+ }
374
+
375
+ return this.dialog.open({ template: container }).then(() => {
376
+ if (!this.validateName(data.key, data.key !== name)) {
377
+ this.pull()
378
+ return
379
+ }
380
+ this.parent.sync.startBatch()
381
+ const oldFields = Utils.CopyJSON(this.parent.properties.fields)
382
+ if (!name) {
383
+ this.add(data)
384
+ } else if (name !== data.key || field.TYPE !== data.type) {
385
+ this.clear()
386
+ // Keep order on rename
387
+ for (const old of oldFields) {
388
+ if (old.key === name) {
389
+ this.add(data)
390
+ } else {
391
+ this.add(old)
392
+ }
393
+ }
394
+ if (name !== data.key) {
395
+ this.parent.renameField(name, data.key)
396
+ }
397
+ } else {
398
+ this.push()
399
+ }
400
+ this.parent.sync.update(
401
+ 'properties.fields',
402
+ this.parent.properties.fields,
403
+ oldFields
404
+ )
405
+ this.parent.sync.commitBatch()
406
+ this.parent.render(['properties.fields'])
407
+ })
408
+ }
409
+
410
+ validateName(name, isNew = false) {
411
+ if (!name) {
412
+ Alert.error(translate('Name cannot be empty.'))
413
+ return false
414
+ }
415
+ if (name.includes('.')) {
416
+ Alert.error(translate('Name “{name}” should not contain a dot.', { name }))
417
+ return false
418
+ }
419
+ if (isNew && this.has(name)) {
420
+ Alert.error(translate('This name already exists: “{name}”', { name }))
421
+ return false
422
+ }
423
+ return true
424
+ }
425
+
426
+ async confirmDelete(name) {
427
+ return this.dialog
428
+ .confirm(translate('Are you sure you want to delete this field on all the data?'))
429
+ .then(() => {
430
+ this.parent.sync.startBatch()
431
+ const oldFields = Utils.CopyJSON(this.parent.properties.fields)
432
+ this.delete(name)
433
+ this.push()
434
+ if (this.parent.filters.has(name)) {
435
+ this.parent.filters.remove(name)
436
+ }
437
+ this.parent.deleteField(name)
438
+ this.parent.sync.update(
439
+ 'properties.fields',
440
+ this.parent.properties.fields,
441
+ oldFields
442
+ )
443
+ this.parent.sync.commitBatch()
444
+ })
445
+ }
446
+ }