umap-project 2.3.1__py3-none-any.whl → 2.4.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of umap-project might be problematic. Click here for more details.

Files changed (204) hide show
  1. umap/__init__.py +1 -1
  2. umap/locale/en/LC_MESSAGES/django.po +81 -31
  3. umap/locale/fr/LC_MESSAGES/django.mo +0 -0
  4. umap/locale/fr/LC_MESSAGES/django.po +117 -66
  5. umap/management/commands/run_websocket_server.py +23 -0
  6. umap/models.py +6 -1
  7. umap/settings/base.py +11 -3
  8. umap/static/umap/base.css +64 -184
  9. umap/static/umap/content.css +3 -2
  10. umap/static/umap/css/dialog.css +18 -0
  11. umap/static/umap/css/icon.css +8 -0
  12. umap/static/umap/css/importers.css +51 -0
  13. umap/static/umap/css/panel.css +18 -57
  14. umap/static/umap/css/tooltip.css +59 -0
  15. umap/static/umap/css/window.css +35 -0
  16. umap/static/umap/img/16-white.svg +1 -3
  17. umap/static/umap/img/alert-icon-error.svg +8 -0
  18. umap/static/umap/img/alert-icon-info.svg +4 -0
  19. umap/static/umap/img/alert-icon-success.svg +3 -0
  20. umap/static/umap/img/icon-external-link.svg +3 -0
  21. umap/static/umap/img/importers/communesfr.svg +5 -0
  22. umap/static/umap/img/importers/datasets.svg +13 -0
  23. umap/static/umap/img/importers/geodatamine.svg +10 -0
  24. umap/static/umap/img/importers/overpass.svg +7 -0
  25. umap/static/umap/img/importers/random.svg +18 -0
  26. umap/static/umap/img/importers/random1.svg +4 -0
  27. umap/static/umap/img/importers/random2.svg +4 -0
  28. umap/static/umap/img/source/16-white.svg +2 -4
  29. umap/static/umap/js/components/alerts/alert.css +160 -0
  30. umap/static/umap/js/components/alerts/alert.js +169 -0
  31. umap/static/umap/js/components/base.js +54 -0
  32. umap/static/umap/js/modules/autocomplete.js +347 -0
  33. umap/static/umap/js/modules/browser.js +6 -6
  34. umap/static/umap/js/modules/caption.js +5 -4
  35. umap/static/umap/js/modules/global.js +36 -12
  36. umap/static/umap/js/modules/help.js +255 -0
  37. umap/static/umap/js/modules/importer.js +308 -0
  38. umap/static/umap/js/modules/importers/communesfr.js +44 -0
  39. umap/static/umap/js/modules/importers/datasets.js +42 -0
  40. umap/static/umap/js/modules/importers/geodatamine.js +95 -0
  41. umap/static/umap/js/modules/importers/overpass.js +84 -0
  42. umap/static/umap/js/modules/request.js +12 -14
  43. umap/static/umap/js/modules/rules.js +241 -0
  44. umap/static/umap/js/modules/schema.js +63 -14
  45. umap/static/umap/js/modules/sync/engine.js +93 -0
  46. umap/static/umap/js/modules/sync/updaters.js +109 -0
  47. umap/static/umap/js/modules/sync/websocket.js +25 -0
  48. umap/static/umap/js/modules/ui/dialog.js +52 -0
  49. umap/static/umap/js/modules/{panel.js → ui/panel.js} +25 -14
  50. umap/static/umap/js/modules/ui/tooltip.js +116 -0
  51. umap/static/umap/js/modules/utils.js +25 -18
  52. umap/static/umap/js/umap.controls.js +13 -14
  53. umap/static/umap/js/umap.core.js +1 -324
  54. umap/static/umap/js/umap.features.js +77 -29
  55. umap/static/umap/js/umap.forms.js +9 -13
  56. umap/static/umap/js/umap.js +254 -215
  57. umap/static/umap/js/umap.layer.js +152 -74
  58. umap/static/umap/js/umap.permissions.js +5 -9
  59. umap/static/umap/js/umap.popup.js +1 -1
  60. umap/static/umap/js/umap.tableeditor.js +8 -8
  61. umap/static/umap/locale/am_ET.js +51 -16
  62. umap/static/umap/locale/am_ET.json +51 -16
  63. umap/static/umap/locale/ar.js +51 -16
  64. umap/static/umap/locale/ar.json +51 -16
  65. umap/static/umap/locale/ast.js +51 -16
  66. umap/static/umap/locale/ast.json +51 -16
  67. umap/static/umap/locale/bg.js +51 -16
  68. umap/static/umap/locale/bg.json +51 -16
  69. umap/static/umap/locale/br.js +55 -20
  70. umap/static/umap/locale/br.json +55 -20
  71. umap/static/umap/locale/ca.js +51 -16
  72. umap/static/umap/locale/ca.json +51 -16
  73. umap/static/umap/locale/cs_CZ.js +93 -58
  74. umap/static/umap/locale/cs_CZ.json +93 -58
  75. umap/static/umap/locale/da.js +51 -16
  76. umap/static/umap/locale/da.json +51 -16
  77. umap/static/umap/locale/de.js +56 -21
  78. umap/static/umap/locale/de.json +56 -21
  79. umap/static/umap/locale/el.js +51 -16
  80. umap/static/umap/locale/el.json +51 -16
  81. umap/static/umap/locale/en.js +52 -16
  82. umap/static/umap/locale/en.json +52 -16
  83. umap/static/umap/locale/en_US.json +51 -16
  84. umap/static/umap/locale/es.js +51 -16
  85. umap/static/umap/locale/es.json +51 -16
  86. umap/static/umap/locale/et.js +51 -16
  87. umap/static/umap/locale/et.json +51 -16
  88. umap/static/umap/locale/eu.js +51 -16
  89. umap/static/umap/locale/eu.json +51 -16
  90. umap/static/umap/locale/fa_IR.js +51 -16
  91. umap/static/umap/locale/fa_IR.json +51 -16
  92. umap/static/umap/locale/fi.js +51 -16
  93. umap/static/umap/locale/fi.json +51 -16
  94. umap/static/umap/locale/fr.js +61 -25
  95. umap/static/umap/locale/fr.json +61 -25
  96. umap/static/umap/locale/gl.js +51 -16
  97. umap/static/umap/locale/gl.json +51 -16
  98. umap/static/umap/locale/he.js +51 -16
  99. umap/static/umap/locale/he.json +51 -16
  100. umap/static/umap/locale/hr.js +51 -16
  101. umap/static/umap/locale/hr.json +51 -16
  102. umap/static/umap/locale/hu.js +51 -16
  103. umap/static/umap/locale/hu.json +51 -16
  104. umap/static/umap/locale/id.js +51 -16
  105. umap/static/umap/locale/id.json +51 -16
  106. umap/static/umap/locale/is.js +51 -16
  107. umap/static/umap/locale/is.json +51 -16
  108. umap/static/umap/locale/it.js +51 -16
  109. umap/static/umap/locale/it.json +51 -16
  110. umap/static/umap/locale/ja.js +51 -16
  111. umap/static/umap/locale/ja.json +51 -16
  112. umap/static/umap/locale/ko.js +51 -16
  113. umap/static/umap/locale/ko.json +51 -16
  114. umap/static/umap/locale/lt.js +51 -16
  115. umap/static/umap/locale/lt.json +51 -16
  116. umap/static/umap/locale/ms.js +51 -16
  117. umap/static/umap/locale/ms.json +51 -16
  118. umap/static/umap/locale/nl.js +51 -16
  119. umap/static/umap/locale/nl.json +51 -16
  120. umap/static/umap/locale/no.js +51 -16
  121. umap/static/umap/locale/no.json +51 -16
  122. umap/static/umap/locale/pl.js +93 -58
  123. umap/static/umap/locale/pl.json +93 -58
  124. umap/static/umap/locale/pl_PL.json +51 -16
  125. umap/static/umap/locale/pt.js +215 -180
  126. umap/static/umap/locale/pt.json +215 -180
  127. umap/static/umap/locale/pt_BR.js +51 -16
  128. umap/static/umap/locale/pt_BR.json +51 -16
  129. umap/static/umap/locale/pt_PT.js +51 -16
  130. umap/static/umap/locale/pt_PT.json +51 -16
  131. umap/static/umap/locale/ro.js +51 -16
  132. umap/static/umap/locale/ro.json +51 -16
  133. umap/static/umap/locale/ru.js +51 -16
  134. umap/static/umap/locale/ru.json +51 -16
  135. umap/static/umap/locale/si.js +51 -16
  136. umap/static/umap/locale/si.json +51 -16
  137. umap/static/umap/locale/sk_SK.js +51 -16
  138. umap/static/umap/locale/sk_SK.json +51 -16
  139. umap/static/umap/locale/sl.js +51 -16
  140. umap/static/umap/locale/sl.json +51 -16
  141. umap/static/umap/locale/sr.js +51 -16
  142. umap/static/umap/locale/sr.json +51 -16
  143. umap/static/umap/locale/sv.js +51 -16
  144. umap/static/umap/locale/sv.json +51 -16
  145. umap/static/umap/locale/th_TH.js +51 -16
  146. umap/static/umap/locale/th_TH.json +51 -16
  147. umap/static/umap/locale/tr.js +51 -16
  148. umap/static/umap/locale/tr.json +51 -16
  149. umap/static/umap/locale/uk_UA.js +51 -16
  150. umap/static/umap/locale/uk_UA.json +51 -16
  151. umap/static/umap/locale/vi.js +51 -16
  152. umap/static/umap/locale/vi.json +51 -16
  153. umap/static/umap/locale/vi_VN.json +51 -16
  154. umap/static/umap/locale/zh.js +51 -16
  155. umap/static/umap/locale/zh.json +51 -16
  156. umap/static/umap/locale/zh_CN.json +51 -16
  157. umap/static/umap/locale/zh_TW.Big5.json +51 -16
  158. umap/static/umap/locale/zh_TW.js +51 -16
  159. umap/static/umap/locale/zh_TW.json +51 -16
  160. umap/static/umap/map.css +40 -53
  161. umap/static/umap/unittests/sync.js +105 -0
  162. umap/static/umap/unittests/utils.js +78 -36
  163. umap/static/umap/vars.css +19 -1
  164. umap/static/umap/vendors/formbuilder/Leaflet.FormBuilder.js +2 -2
  165. umap/templates/umap/components/alerts/alert.html +89 -0
  166. umap/templates/umap/content.html +4 -3
  167. umap/templates/umap/css.html +4 -0
  168. umap/templates/umap/home.html +3 -0
  169. umap/templates/umap/js.html +0 -3
  170. umap/templates/umap/map_init.html +2 -8
  171. umap/templates/umap/messages.html +9 -11
  172. umap/templates/umap/search.html +3 -0
  173. umap/tests/base.py +2 -0
  174. umap/tests/integration/conftest.py +30 -0
  175. umap/tests/integration/test_anonymous_owned_map.py +8 -13
  176. umap/tests/integration/test_browser.py +77 -4
  177. umap/tests/integration/test_conditional_rules.py +201 -0
  178. umap/tests/integration/test_dashboard.py +1 -1
  179. umap/tests/integration/test_datalayer.py +2 -3
  180. umap/tests/integration/test_edit_datalayer.py +4 -4
  181. umap/tests/integration/test_edit_map.py +1 -1
  182. umap/tests/integration/test_facets_browser.py +3 -3
  183. umap/tests/integration/test_import.py +185 -49
  184. umap/tests/integration/test_map.py +31 -2
  185. umap/tests/integration/{test_collaborative_editing.py → test_optimistic_merge.py} +7 -7
  186. umap/tests/integration/test_owned_map.py +1 -1
  187. umap/tests/integration/test_picto.py +2 -2
  188. umap/tests/integration/test_statics.py +1 -1
  189. umap/tests/integration/test_view_marker.py +2 -2
  190. umap/tests/integration/test_websocket_sync.py +283 -0
  191. umap/tests/settings.py +5 -0
  192. umap/tests/test_datalayer_views.py +0 -1
  193. umap/tests/test_views.py +53 -0
  194. umap/urls.py +5 -0
  195. umap/views.py +40 -11
  196. umap/websocket_server.py +92 -0
  197. {umap_project-2.3.1.dist-info → umap_project-2.4.0.dist-info}/METADATA +10 -8
  198. {umap_project-2.3.1.dist-info → umap_project-2.4.0.dist-info}/RECORD +201 -167
  199. umap/static/umap/js/umap.autocomplete.js +0 -341
  200. umap/static/umap/js/umap.importer.js +0 -187
  201. umap/static/umap/js/umap.ui.js +0 -190
  202. {umap_project-2.3.1.dist-info → umap_project-2.4.0.dist-info}/WHEEL +0 -0
  203. {umap_project-2.3.1.dist-info → umap_project-2.4.0.dist-info}/entry_points.txt +0 -0
  204. {umap_project-2.3.1.dist-info → umap_project-2.4.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,255 @@
1
+ import { DomUtil, DomEvent } from '../../vendors/leaflet/leaflet-src.esm.js'
2
+ import { translate } from './i18n.js'
3
+
4
+ const SHORTCUTS = {
5
+ DRAW_MARKER: {
6
+ shortcut: 'Modifier+M',
7
+ label: translate('Draw a marker'),
8
+ },
9
+ DRAW_LINE: {
10
+ shortcut: 'Modifier+L',
11
+ label: translate('Draw a polyline'),
12
+ },
13
+ DRAW_POLYGON: {
14
+ shortcut: 'Modifier+P',
15
+ label: translate('Draw a polygon'),
16
+ },
17
+ TOGGLE_EDIT: {
18
+ shortcut: 'Modifier+E',
19
+ label: translate('Toggle edit mode'),
20
+ },
21
+ STOP_EDIT: {
22
+ shortcut: 'Modifier+E',
23
+ label: translate('Stop editing'),
24
+ },
25
+ SAVE_MAP: {
26
+ shortcut: 'Modifier+S',
27
+ label: translate('Save map'),
28
+ },
29
+ IMPORT_PANEL: {
30
+ shortcut: 'Modifier+I',
31
+ label: translate('Import data'),
32
+ },
33
+ SEARCH: {
34
+ shortcut: 'Modifier+F',
35
+ label: translate('Search location'),
36
+ },
37
+ CANCEL: {
38
+ shortcut: 'Modifier+Z',
39
+ label: translate('Cancel edits'),
40
+ },
41
+ PREVIEW: {
42
+ shortcut: 'Modifier+E',
43
+ label: translate('Back to preview'),
44
+ },
45
+ SAVE: {
46
+ shortcut: 'Modifier+S',
47
+ label: translate('Save current edits'),
48
+ },
49
+ EDIT_FEATURE_LAYER: {
50
+ shortcut: 'Modifier+⇧+Click',
51
+ label: translate("Edit feature's layer"),
52
+ },
53
+ CONTINUE_LINE: {
54
+ shortcut: 'Modifier+Click',
55
+ label: translate('Continue line'),
56
+ },
57
+ }
58
+
59
+ const ENTRIES = {
60
+ formatURL: `${translate(
61
+ 'Supported variables that will be dynamically replaced'
62
+ )}: {bbox}, {lat}, {lng}, {zoom}, {east}, {north}..., {left}, {top}..., locale, lang`,
63
+ colorValue: translate('Must be a valid CSS value (eg.: DarkBlue or #123456)'),
64
+ smoothFactor: translate(
65
+ 'How much to simplify the polyline on each zoom level (more = better performance and smoother look, less = more accurate)'
66
+ ),
67
+ dashArray: translate(
68
+ 'A comma separated list of numbers that defines the stroke dash pattern. Ex.: "5, 10, 15".'
69
+ ),
70
+ zoomTo: translate('Zoom level for automatic zooms'),
71
+ labelKey: translate(
72
+ 'The name of the property to use as feature label (eg.: "nom"). You can also use properties inside brackets to use more than one or mix with static content (eg.: "{name} in {place}")'
73
+ ),
74
+ stroke: translate('Whether to display or not polygons paths.'),
75
+ fill: translate('Whether to fill polygons with color.'),
76
+ fillColor: translate('Optional. Same as color if not set.'),
77
+ shortCredit: translate('Will be displayed in the bottom right corner of the map'),
78
+ longCredit: translate('Will be visible in the caption of the map'),
79
+ permanentCredit: translate(
80
+ 'Will be permanently visible in the bottom left corner of the map'
81
+ ),
82
+ sortKey: translate(
83
+ 'Comma separated list of properties to use for sorting features. To reverse the sort, put a minus sign (-) before. Eg. mykey,-otherkey.'
84
+ ),
85
+ slugKey: translate('The name of the property to use as feature unique identifier.'),
86
+ filterKey: translate(
87
+ 'Comma separated list of properties to use when filtering features by text input'
88
+ ),
89
+ facetKey: translate(
90
+ 'Comma separated list of properties to use for filters (eg.: mykey,otherkey). To control label, add it after a | (eg.: mykey|My Key,otherkey|Other Key). To control input field type, add it after another | (eg.: mykey|My Key|checkbox,otherkey|Other Key|datetime). Allowed values for the input field type are checkbox (default), radio, number, date and datetime.'
91
+ ),
92
+ interactive: translate(
93
+ 'If false, the polygon or line will act as a part of the underlying map.'
94
+ ),
95
+ outlink: translate('Define link to open in a new window on polygon click.'),
96
+ dynamicRemoteData: translate('Fetch data each time map view changes.'),
97
+ proxyRemoteData: translate(
98
+ "To use if remote server doesn't allow cross domain (slower)"
99
+ ),
100
+ browsable: translate(
101
+ 'Set it to false to hide this layer from the slideshow, the data browser, the popup navigation…'
102
+ ),
103
+ importMode: translate(
104
+ 'When providing an URL, uMap can copy the remote data in a layer, or add this URL as remote source of the layer. In that case, data will always be fetched from that URL, and thus be up to date, but it will not be possible to edit it inside uMap.'
105
+ ),
106
+ importFormats: `
107
+ <div>
108
+ <dt>GeoJSON</dt>
109
+ <dd>${translate('All properties are imported.')}</dd>
110
+ <dt>GPX</dt>
111
+ <dd>${translate('Properties imported:')}name, desc</dd>
112
+ <dt>KML</dt>
113
+ <dd>${translate('Properties imported:')}name, description</dd>
114
+ <dt>CSV</dt>
115
+ <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 begining of the header, case insensitive. All other column are imported as properties.')}</dd>
116
+ <dt>uMap</dt>
117
+ <dd>${translate('Imports all umap data, including layers and settings.')}</dd>
118
+ </div>
119
+ `,
120
+ dynamicProperties: `
121
+ <div>
122
+ <h4>${translate('Dynamic properties')}</h4>
123
+ <p>${translate('Use placeholders with feature properties between brackets, eg. &#123;name&#125;, they will be dynamically replaced by the corresponding values.')}</p>
124
+ </div>
125
+ `,
126
+
127
+ textFormatting: `
128
+ <div>
129
+ <h4>${translate('Text formatting')}</h4>
130
+ <ul>
131
+ <li>${translate('*single star for italic*')}</li>
132
+ <li>${translate('**double star for bold**')}</li>
133
+ <li>${translate('# one hash for main heading')}</li>
134
+ <li>${translate('## two hashes for second heading')}</li>
135
+ <li>${translate('### three hashes for third heading')}</li>
136
+ <li>${translate('Simple link: [[http://example.com]]')}</li>
137
+ <li>${translate('Link with text: [[http://example.com|text of the link]]')}</li>
138
+ <li>${translate('Image: {{http://image.url.com}}')}</li>
139
+ <li>${translate('Image with custom width (in px): {{http://image.url.com|width}}')}</li>
140
+ <li>${translate('Iframe: {{{http://iframe.url.com}}}')}</li>
141
+ <li>${translate('Iframe with custom height (in px): {{{http://iframe.url.com|height}}}')}</li>
142
+ <li>${translate('Iframe with custom height and width (in px): {{{http://iframe.url.com|height*width}}}')}</li>
143
+ <li>${translate('--- for a horizontal rule')}</li>
144
+ </ul>
145
+ </div>
146
+ `,
147
+
148
+ overpassImporter: `
149
+ <div>
150
+ <h4>${translate('Overpass supported expressions')}</h4>
151
+ <ul>
152
+ <li>${translate('key (eg. building)')}</li>
153
+ <li>${translate('!key (eg. !name)')}</li>
154
+ <li>${translate('key=value (eg. building=yes)')}</li>
155
+ <li>${translate('key!=value (eg. building!=yes)')}</li>
156
+ <li>${translate('key~value (eg. name~Grisy)')}</li>
157
+ <li>${translate('key="value|value2" (eg. name="Paris|Berlin")')}</li>
158
+ </ul>
159
+ <div>${translate('More info about Overpass syntax')}: <a href="https://wiki.openstreetmap.org/wiki/Overpass_API/Language_Guide">https://wiki.openstreetmap.org/wiki/Overpass_API/Language_Guide</a></div>
160
+ <div>${translate('For more complex needs, see')} <a href="https://overpass-turbo.eu/">https://overpass-turbo.eu/</a></div>
161
+ </div>
162
+ `,
163
+ }
164
+
165
+ export default class Help {
166
+ constructor(map) {
167
+ this.map = map
168
+ this.isMacOS = /mac/i.test(
169
+ // eslint-disable-next-line compat/compat -- Fallback available.
170
+ navigator.userAgentData ? navigator.userAgentData.platform : navigator.platform
171
+ )
172
+ }
173
+
174
+ displayLabel(action, withKbdTag = true) {
175
+ let { shortcut, label } = SHORTCUTS[action]
176
+ const modifier = this.isMacOS ? 'Cmd' : 'Ctrl'
177
+ shortcut = shortcut.replace('Modifier', modifier)
178
+ if (withKbdTag) {
179
+ shortcut = shortcut
180
+ .split('+')
181
+ .map((el) => `<kbd>${el}</kbd>`)
182
+ .join('+')
183
+ label += ` ${shortcut}`
184
+ } else {
185
+ label += ` (${shortcut})`
186
+ }
187
+ return label
188
+ }
189
+
190
+ show(entries) {
191
+ const container = DomUtil.add('div')
192
+ DomUtil.createTitle(container, translate('Help'))
193
+ // Special dynamic case. Do we still think this dialog is usefull ?
194
+ if (entries == 'edit') {
195
+ DomUtil.element({
196
+ tagName: 'div',
197
+ className: 'umap-help-entry',
198
+ parent: container,
199
+ }).appendChild(this._buildEditEntry())
200
+ } else {
201
+ for (const name of entries) {
202
+ DomUtil.element({
203
+ tagName: 'div',
204
+ className: 'umap-help-entry',
205
+ parent: container,
206
+ innerHTML: ENTRIES[name],
207
+ })
208
+ }
209
+ }
210
+ this.map.dialog.open({ content: container, className: 'dark' })
211
+ }
212
+
213
+ button(container, entries, classname) {
214
+ const button = DomUtil.createButton(
215
+ classname || 'umap-help-button',
216
+ container,
217
+ translate('Help')
218
+ )
219
+ entries = typeof entries === 'string' ? [entries] : entries
220
+ DomEvent.on(button, 'click', DomEvent.stop).on(button, 'click', () =>
221
+ this.show(entries)
222
+ )
223
+ return button
224
+ }
225
+
226
+ link(container, entries) {
227
+ const button = this.button(container, entries, 'umap-help-link')
228
+ button.textContent = translate('Help')
229
+ return button
230
+ }
231
+
232
+ parse(container) {
233
+ for (const element of container.querySelectorAll('[data-help]')) {
234
+ this.button(element, element.dataset.help.split(','))
235
+ }
236
+ }
237
+
238
+ _buildEditEntry() {
239
+ const container = DomUtil.create('div', '')
240
+ const title = DomUtil.create('h4', '', container)
241
+ const actionsContainer = DomUtil.create('ul', 'umap-edit-actions', container)
242
+ const addAction = (action) => {
243
+ const actionContainer = DomUtil.add('li', '', actionsContainer)
244
+ DomUtil.add('i', action.options.className, actionContainer),
245
+ DomUtil.add('span', '', actionContainer, action.options.tooltip)
246
+ DomEvent.on(actionContainer, 'click', action.addHooks, action)
247
+ DomEvent.on(actionContainer, 'click', this.map.dialog.close, this.map.dialog)
248
+ }
249
+ title.textContent = translate('Where do we go from here?')
250
+ for (const id in this.map.helpMenuActions) {
251
+ addAction(this.map.helpMenuActions[id])
252
+ }
253
+ return container
254
+ }
255
+ }
@@ -0,0 +1,308 @@
1
+ import { DomUtil, DomEvent } from '../../vendors/leaflet/leaflet-src.esm.js'
2
+ import { translate } from './i18n.js'
3
+ import { uMapAlert as Alert } from '../components/alerts/alert.js'
4
+ import Dialog from './ui/dialog.js'
5
+ import { SCHEMA } from './schema.js'
6
+ import * as Utils from './utils.js'
7
+
8
+ const TEMPLATE = `
9
+ <h3><i class="icon icon-16 icon-upload"></i><span>${translate('Import data')}</span></h3>
10
+ <fieldset class="formbox">
11
+ <legend class="counter">${translate('Choose data')}</legend>
12
+ <input type="file" multiple autofocus onchange />
13
+ <input type="url" placeholder="${translate('Provide an URL here')}" onchange />
14
+ <textarea onchange placeholder="${translate('Paste your data here')}"></textarea>
15
+ <div class="importers" hidden>
16
+ <h4>${translate('Import helpers:')}</h4>
17
+ <ul class="grid-container">
18
+ </ul>
19
+ </div>
20
+ </fieldset>
21
+ <fieldset class="formbox">
22
+ <legend class="counter" data-help="importFormats">${translate(
23
+ 'Choose the format'
24
+ )}</legend>
25
+ <select name="format" onchange></select>
26
+ </fieldset>
27
+ <fieldset id="destination" class="formbox">
28
+ <legend class="counter">${translate('Choose the layer')}</legend>
29
+ <select name="layer-id" onchange></select>
30
+ <label id="clear">
31
+ <input type="checkbox" name="clear" />
32
+ ${translate('Replace layer content')}
33
+ </label>
34
+ <input type="text" name="layer-name" placeholder="${translate('Layer name')}" />
35
+ </fieldset>
36
+ <fieldset id="import-mode" class="formbox">
37
+ <legend class="counter" data-help="importMode">${translate('Choose import mode')}</legend>
38
+ <label>
39
+ <input type="radio" name="action" value="copy" />
40
+ ${translate('Copy into the layer')}
41
+ </label>
42
+ <label>
43
+ <input type="radio" name="action" value="link" />
44
+ ${translate('Link to the layer as remote data')}
45
+ </label>
46
+ </fieldset>
47
+ <input type="button" class="button" name="submit" value="${translate('Import data')}" />
48
+ `
49
+
50
+ export default class Importer {
51
+ constructor(map) {
52
+ this.map = map
53
+ this.TYPES = ['geojson', 'csv', 'gpx', 'kml', 'osm', 'georss', 'umap']
54
+ this.IMPORTERS = []
55
+ this.loadImporters()
56
+ this.dialog = new Dialog(this.map._controlContainer)
57
+ }
58
+
59
+ loadImporters() {
60
+ for (const [name, config] of Object.entries(this.map.options.importers || {})) {
61
+ const register = (mod) => {
62
+ this.IMPORTERS.push(new mod.Importer(this.map, config))
63
+ }
64
+ // We need to have explicit static paths for Django's collectstatic with hashes.
65
+ switch (name) {
66
+ case 'geodatamine':
67
+ import('./importers/geodatamine.js').then(register)
68
+ break
69
+ case 'communesfr':
70
+ import('./importers/communesfr.js').then(register)
71
+ break
72
+ case 'overpass':
73
+ import('./importers/overpass.js').then(register)
74
+ break
75
+ case 'datasets':
76
+ import('./importers/datasets.js').then(register)
77
+ break
78
+ }
79
+ }
80
+ }
81
+
82
+ qs(query) {
83
+ return this.container.querySelector(query)
84
+ }
85
+
86
+ get url() {
87
+ return this.qs('[type=url]').value
88
+ }
89
+
90
+ set url(value) {
91
+ this.qs('[type=url]').value = value
92
+ this.onChange()
93
+ }
94
+
95
+ get format() {
96
+ return this.qs('[name=format]').value
97
+ }
98
+
99
+ set format(value) {
100
+ this.qs('[name=format]').value = value
101
+ this.onChange()
102
+ }
103
+
104
+ get files() {
105
+ return this.qs('[type=file]').files
106
+ }
107
+
108
+ get raw() {
109
+ return this.qs('textarea').value
110
+ }
111
+
112
+ get clear() {
113
+ return Boolean(this.qs('[name=clear]').checked)
114
+ }
115
+
116
+ get action() {
117
+ return this.qs('[name=action]:checked').value
118
+ }
119
+
120
+ get layerId() {
121
+ return this.qs('[name=layer-id]').value
122
+ }
123
+
124
+ set layerId(value) {
125
+ this.qs('[name=layer-id]').value = value
126
+ }
127
+
128
+ get layerName() {
129
+ return this.qs('[name=layer-name]').value
130
+ }
131
+
132
+ set layerName(name) {
133
+ this.qs('[name=layer-name]').value = name
134
+ this.onChange()
135
+ }
136
+
137
+ get layer() {
138
+ return (
139
+ this.map.datalayers[this.layerId] ||
140
+ this.map.createDataLayer({ name: this.layerName })
141
+ )
142
+ }
143
+
144
+ build() {
145
+ this.container = DomUtil.create('div', 'umap-upload')
146
+ this.container.innerHTML = TEMPLATE
147
+ if (this.IMPORTERS.length) {
148
+ const parent = this.container.querySelector('.importers ul')
149
+ for (const plugin of this.IMPORTERS.sort((a, b) => (a.id > b.id ? 1 : -1))) {
150
+ L.DomUtil.createButton(
151
+ plugin.id,
152
+ DomUtil.element({ tagName: 'li', parent }),
153
+ plugin.name,
154
+ () => plugin.open(this)
155
+ )
156
+ }
157
+ this.qs('.importers').toggleAttribute('hidden', false)
158
+ }
159
+ for (const type of this.TYPES) {
160
+ DomUtil.element({
161
+ tagName: 'option',
162
+ parent: this.qs('[name=format]'),
163
+ value: type,
164
+ textContent: type,
165
+ })
166
+ }
167
+ this.map.help.parse(this.container)
168
+ DomEvent.on(this.qs('[name=submit]'), 'click', this.submit, this)
169
+ DomEvent.on(this.qs('[type=file]'), 'change', this.onFileChange, this)
170
+ for (const element of this.container.querySelectorAll('[onchange]')) {
171
+ DomEvent.on(element, 'change', this.onChange, this)
172
+ }
173
+ }
174
+
175
+ onChange() {
176
+ this.qs('#destination').toggleAttribute('hidden', this.format === 'umap')
177
+ this.qs('#import-mode').toggleAttribute(
178
+ 'hidden',
179
+ this.format === 'umap' || !this.url
180
+ )
181
+ this.qs('[name=layer-name]').toggleAttribute('hidden', Boolean(this.layerId))
182
+ this.qs('#clear').toggleAttribute('hidden', !Boolean(this.layerId))
183
+ }
184
+
185
+ onFileChange(e) {
186
+ let type = '',
187
+ newType
188
+ for (const file of e.target.files) {
189
+ newType = U.Utils.detectFileType(file)
190
+ if (!type && newType) type = newType
191
+ if (type && newType !== type) {
192
+ type = ''
193
+ break
194
+ }
195
+ }
196
+ this.format = type
197
+ }
198
+
199
+ onLoad() {
200
+ this.qs('[type=file]').value = null
201
+ this.url = null
202
+ this.format = undefined
203
+ this.layerName = null
204
+ const layerSelect = this.qs('[name="layer-id"]')
205
+ layerSelect.innerHTML = ''
206
+ this.map.eachDataLayerReverse((datalayer) => {
207
+ if (datalayer.isLoaded() && !datalayer.isRemoteLayer()) {
208
+ DomUtil.element({
209
+ tagName: 'option',
210
+ parent: layerSelect,
211
+ textContent: datalayer.options.name,
212
+ value: L.stamp(datalayer),
213
+ })
214
+ }
215
+ })
216
+ DomUtil.element({
217
+ tagName: 'option',
218
+ value: '',
219
+ textContent: translate('Import in a new layer'),
220
+ parent: layerSelect,
221
+ selected: true,
222
+ })
223
+ }
224
+
225
+ open() {
226
+ if (!this.container) this.build()
227
+ const onLoad = this.map.editPanel.open({ content: this.container })
228
+ onLoad.then(() => this.onLoad())
229
+ }
230
+
231
+ openFiles() {
232
+ this.open()
233
+ this.qs('[type=file]').showPicker()
234
+ }
235
+
236
+ submit() {
237
+ let hasErrors = false
238
+ if (this.format === 'umap') {
239
+ hasErrors = !this.full()
240
+ } else if (!this.url) {
241
+ hasErrors = !this.copy()
242
+ } else if (this.action) {
243
+ hasErrors = !this[this.action]()
244
+ }
245
+ if (!hasErrors) {
246
+ Alert.info(translate('Data successfully imported!'))
247
+ }
248
+ }
249
+
250
+ full() {
251
+ this.map.once('postsync', this.map._setDefaultCenter)
252
+ try {
253
+ if (this.files.length) {
254
+ for (const file of this.files) {
255
+ this.map.processFileToImport(file, null, 'umap')
256
+ }
257
+ } else if (this.raw) {
258
+ this.map.importRaw(this.raw)
259
+ } else if (this.url) {
260
+ this.map.importFromUrl(this.url, this.format)
261
+ }
262
+ } catch (e) {
263
+ Alert.error(translate('Invalid umap data'))
264
+ console.error(e)
265
+ return false
266
+ }
267
+ }
268
+
269
+ link() {
270
+ if (!this.url) {
271
+ return false
272
+ }
273
+ if (!this.format) {
274
+ Alert.error(translate('Please choose a format'))
275
+ return false
276
+ }
277
+ const layer = this.layer
278
+ layer.options.remoteData = {
279
+ url: this.url,
280
+ format: this.format,
281
+ }
282
+ if (this.map.options.urls.ajax_proxy) {
283
+ layer.options.remoteData.proxy = true
284
+ layer.options.remoteData.ttl = SCHEMA.ttl.default
285
+ }
286
+ layer.fetchRemoteData(true)
287
+ }
288
+
289
+ copy() {
290
+ // Format may be guessed from file later.
291
+ // Usefull in case of multiple files with different formats.
292
+ if (!this.format && !this.files.length) {
293
+ Alert.error(translate('Please choose a format'))
294
+ return false
295
+ }
296
+ const layer = this.layer
297
+ if (this.clear) layer.empty()
298
+ if (this.files.length) {
299
+ for (const file of this.files) {
300
+ this.map.processFileToImport(file, layer, this.format)
301
+ }
302
+ } else if (this.raw) {
303
+ layer.importRaw(this.raw, this.format)
304
+ } else if (this.url) {
305
+ layer.importFromUrl(this.url, this.format)
306
+ }
307
+ }
308
+ }
@@ -0,0 +1,44 @@
1
+ import { DomUtil } from '../../../vendors/leaflet/leaflet-src.esm.js'
2
+ import { BaseAjax, SingleMixin } from '../autocomplete.js'
3
+
4
+ class Autocomplete extends SingleMixin(BaseAjax) {
5
+ createResult(item) {
6
+ return super.createResult({
7
+ value: item.code,
8
+ label: `${item.nom} (${item.code})`,
9
+ })
10
+ }
11
+ }
12
+
13
+ export class Importer {
14
+ constructor(map, options) {
15
+ this.name = options.name || 'Communes'
16
+ this.id = 'communesfr'
17
+ }
18
+
19
+ async open(importer) {
20
+ const container = DomUtil.create('div')
21
+ DomUtil.createTitle(container, this.name)
22
+ DomUtil.element({
23
+ tagName: 'p',
24
+ parent: container,
25
+ textContent: "Importer les contours d'une commune française.",
26
+ })
27
+ const options = {
28
+ placeholder: 'Commune…',
29
+ url: 'https://geo.api.gouv.fr/communes?nom={q}&limit=5',
30
+ on_select: (choice) => {
31
+ importer.url = `https://geo.api.gouv.fr/communes?code=${choice.item.value}&format=geojson&geometry=contour`
32
+ importer.format = 'geojson'
33
+ importer.layerName = choice.item.label
34
+ importer.dialog.close()
35
+ },
36
+ }
37
+ this.autocomplete = new Autocomplete(container, options)
38
+
39
+ importer.dialog.open({
40
+ content: container,
41
+ className: `${this.id} importer dark`,
42
+ })
43
+ }
44
+ }
@@ -0,0 +1,42 @@
1
+ import { DomUtil } from '../../../vendors/leaflet/leaflet-src.esm.js'
2
+ import { translate } from '../i18n.js'
3
+
4
+ export class Importer {
5
+ constructor(map, options) {
6
+ this.name = options.name || 'Datasets'
7
+ this.choices = options?.choices
8
+ this.id = 'datasets'
9
+ }
10
+
11
+ async open(importer) {
12
+ const container = DomUtil.create('div', 'formbox')
13
+ DomUtil.element({ tagName: 'h3', textContent: this.name, parent: container })
14
+ const select = DomUtil.create('select', '', container)
15
+ const noPreset = DomUtil.element({
16
+ tagName: 'option',
17
+ parent: select,
18
+ value: '',
19
+ textContent: translate('Choose a dataset'),
20
+ })
21
+ for (const dataset of this.choices) {
22
+ const option = DomUtil.create('option', '', select)
23
+ option.value = dataset.url
24
+ option.textContent = dataset.label
25
+ option.dataset.format = dataset.format || 'geojson'
26
+ }
27
+ const confirm = () => {
28
+ if (select.value) {
29
+ importer.url = select.value
30
+ importer.format = select.options[select.selectedIndex].dataset.format
31
+ importer.layerName = select.options[select.selectedIndex].textContent
32
+ }
33
+ importer.dialog.close()
34
+ }
35
+ L.DomUtil.createButton('', container, translate('Choose this dataset'), confirm)
36
+
37
+ importer.dialog.open({
38
+ content: container,
39
+ className: `${this.id} importer dark`,
40
+ })
41
+ }
42
+ }