umap-project 1.12.2__py3-none-any.whl → 1.13.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 (158) hide show
  1. umap/__init__.py +1 -1
  2. umap/locale/br/LC_MESSAGES/django.mo +0 -0
  3. umap/locale/br/LC_MESSAGES/django.po +82 -54
  4. umap/locale/cs_CZ/LC_MESSAGES/django.mo +0 -0
  5. umap/locale/cs_CZ/LC_MESSAGES/django.po +82 -54
  6. umap/locale/el/LC_MESSAGES/django.mo +0 -0
  7. umap/locale/el/LC_MESSAGES/django.po +86 -58
  8. umap/locale/en/LC_MESSAGES/django.po +79 -51
  9. umap/locale/es/LC_MESSAGES/django.mo +0 -0
  10. umap/locale/es/LC_MESSAGES/django.po +82 -54
  11. umap/locale/fr/LC_MESSAGES/django.mo +0 -0
  12. umap/locale/fr/LC_MESSAGES/django.po +84 -56
  13. umap/locale/hu/LC_MESSAGES/django.mo +0 -0
  14. umap/locale/hu/LC_MESSAGES/django.po +108 -80
  15. umap/locale/it/LC_MESSAGES/django.mo +0 -0
  16. umap/locale/it/LC_MESSAGES/django.po +82 -54
  17. umap/locale/ms/LC_MESSAGES/django.mo +0 -0
  18. umap/locale/ms/LC_MESSAGES/django.po +82 -54
  19. umap/locale/pl/LC_MESSAGES/django.mo +0 -0
  20. umap/locale/pl/LC_MESSAGES/django.po +82 -54
  21. umap/locale/sv/LC_MESSAGES/django.mo +0 -0
  22. umap/locale/sv/LC_MESSAGES/django.po +82 -54
  23. umap/locale/zh_TW/LC_MESSAGES/django.mo +0 -0
  24. umap/locale/zh_TW/LC_MESSAGES/django.po +86 -58
  25. umap/models.py +29 -0
  26. umap/static/umap/base.css +33 -5
  27. umap/static/umap/content.css +41 -2
  28. umap/static/umap/img/16.svg +12 -2
  29. umap/static/umap/img/source/16.svg +165 -820
  30. umap/static/umap/js/umap.browser.js +9 -3
  31. umap/static/umap/js/umap.controls.js +45 -217
  32. umap/static/umap/js/umap.core.js +66 -30
  33. umap/static/umap/js/umap.forms.js +18 -31
  34. umap/static/umap/js/umap.icon.js +44 -21
  35. umap/static/umap/js/umap.js +5 -26
  36. umap/static/umap/js/umap.layer.js +68 -25
  37. umap/static/umap/js/umap.popup.js +87 -0
  38. umap/static/umap/js/umap.share.js +254 -0
  39. umap/static/umap/js/umap.ui.js +4 -2
  40. umap/static/umap/locale/am_ET.js +19 -9
  41. umap/static/umap/locale/am_ET.json +19 -9
  42. umap/static/umap/locale/ar.js +19 -9
  43. umap/static/umap/locale/ar.json +19 -9
  44. umap/static/umap/locale/ast.js +19 -9
  45. umap/static/umap/locale/ast.json +19 -9
  46. umap/static/umap/locale/bg.js +19 -9
  47. umap/static/umap/locale/bg.json +19 -9
  48. umap/static/umap/locale/br.js +22 -12
  49. umap/static/umap/locale/br.json +22 -12
  50. umap/static/umap/locale/ca.js +19 -9
  51. umap/static/umap/locale/ca.json +19 -9
  52. umap/static/umap/locale/cs_CZ.js +19 -9
  53. umap/static/umap/locale/cs_CZ.json +19 -9
  54. umap/static/umap/locale/da.js +19 -9
  55. umap/static/umap/locale/da.json +19 -9
  56. umap/static/umap/locale/de.js +19 -9
  57. umap/static/umap/locale/de.json +19 -9
  58. umap/static/umap/locale/el.js +19 -9
  59. umap/static/umap/locale/el.json +19 -9
  60. umap/static/umap/locale/en.js +19 -9
  61. umap/static/umap/locale/en.json +19 -9
  62. umap/static/umap/locale/en_US.json +19 -9
  63. umap/static/umap/locale/es.js +19 -9
  64. umap/static/umap/locale/es.json +19 -9
  65. umap/static/umap/locale/et.js +19 -9
  66. umap/static/umap/locale/et.json +19 -9
  67. umap/static/umap/locale/fa_IR.js +19 -9
  68. umap/static/umap/locale/fa_IR.json +19 -9
  69. umap/static/umap/locale/fi.js +19 -9
  70. umap/static/umap/locale/fi.json +19 -9
  71. umap/static/umap/locale/fr.js +19 -9
  72. umap/static/umap/locale/fr.json +19 -9
  73. umap/static/umap/locale/gl.js +19 -9
  74. umap/static/umap/locale/gl.json +19 -9
  75. umap/static/umap/locale/he.js +19 -9
  76. umap/static/umap/locale/he.json +19 -9
  77. umap/static/umap/locale/hr.js +19 -9
  78. umap/static/umap/locale/hr.json +19 -9
  79. umap/static/umap/locale/hu.js +19 -9
  80. umap/static/umap/locale/hu.json +19 -9
  81. umap/static/umap/locale/id.js +19 -9
  82. umap/static/umap/locale/id.json +19 -9
  83. umap/static/umap/locale/is.js +19 -9
  84. umap/static/umap/locale/is.json +19 -9
  85. umap/static/umap/locale/it.js +19 -9
  86. umap/static/umap/locale/it.json +19 -9
  87. umap/static/umap/locale/ja.js +19 -9
  88. umap/static/umap/locale/ja.json +19 -9
  89. umap/static/umap/locale/ko.js +19 -9
  90. umap/static/umap/locale/ko.json +19 -9
  91. umap/static/umap/locale/lt.js +19 -9
  92. umap/static/umap/locale/lt.json +19 -9
  93. umap/static/umap/locale/ms.js +19 -9
  94. umap/static/umap/locale/ms.json +19 -9
  95. umap/static/umap/locale/nl.js +19 -9
  96. umap/static/umap/locale/nl.json +19 -9
  97. umap/static/umap/locale/no.js +19 -9
  98. umap/static/umap/locale/no.json +19 -9
  99. umap/static/umap/locale/pl.js +19 -9
  100. umap/static/umap/locale/pl.json +19 -9
  101. umap/static/umap/locale/pl_PL.json +19 -9
  102. umap/static/umap/locale/pt.js +19 -9
  103. umap/static/umap/locale/pt.json +19 -9
  104. umap/static/umap/locale/pt_BR.js +19 -9
  105. umap/static/umap/locale/pt_BR.json +19 -9
  106. umap/static/umap/locale/pt_PT.js +19 -9
  107. umap/static/umap/locale/pt_PT.json +19 -9
  108. umap/static/umap/locale/ro.js +19 -9
  109. umap/static/umap/locale/ro.json +19 -9
  110. umap/static/umap/locale/ru.js +19 -9
  111. umap/static/umap/locale/ru.json +19 -9
  112. umap/static/umap/locale/sk_SK.js +19 -9
  113. umap/static/umap/locale/sk_SK.json +19 -9
  114. umap/static/umap/locale/sl.js +19 -9
  115. umap/static/umap/locale/sl.json +19 -9
  116. umap/static/umap/locale/sr.js +19 -9
  117. umap/static/umap/locale/sr.json +19 -9
  118. umap/static/umap/locale/sv.js +19 -9
  119. umap/static/umap/locale/sv.json +19 -9
  120. umap/static/umap/locale/th_TH.js +19 -9
  121. umap/static/umap/locale/th_TH.json +19 -9
  122. umap/static/umap/locale/tr.js +19 -9
  123. umap/static/umap/locale/tr.json +19 -9
  124. umap/static/umap/locale/uk_UA.js +19 -9
  125. umap/static/umap/locale/uk_UA.json +19 -9
  126. umap/static/umap/locale/vi.js +19 -9
  127. umap/static/umap/locale/vi.json +19 -9
  128. umap/static/umap/locale/vi_VN.json +19 -9
  129. umap/static/umap/locale/zh.js +19 -9
  130. umap/static/umap/locale/zh.json +19 -9
  131. umap/static/umap/locale/zh_CN.json +19 -9
  132. umap/static/umap/locale/zh_TW.Big5.json +19 -9
  133. umap/static/umap/locale/zh_TW.js +55 -45
  134. umap/static/umap/locale/zh_TW.json +55 -45
  135. umap/static/umap/map.css +76 -9
  136. umap/static/umap/test/Map.Export.js +3 -3
  137. umap/static/umap/test/Polygon.js +2 -2
  138. umap/static/umap/test/Polyline.js +2 -0
  139. umap/static/umap/test/index.html +1 -0
  140. umap/static/umap/vendors/leaflet/leaflet-src.esm.js +7055 -7054
  141. umap/static/umap/vendors/toolbar/leaflet.toolbar-src.js +1 -1
  142. umap/templates/auth/user_form.html +1 -1
  143. umap/templates/umap/content.html +1 -1
  144. umap/templates/umap/js.html +1 -0
  145. umap/templates/umap/map_list.html +1 -1
  146. umap/templates/umap/map_table.html +67 -35
  147. umap/templates/umap/user_dashboard.html +23 -1
  148. umap/templatetags/umap_tags.py +7 -27
  149. umap/tests/integration/test_edit_datalayer.py +45 -0
  150. umap/tests/integration/test_export_map.py +2 -3
  151. umap/tests/integration/test_facets_browser.py +86 -0
  152. umap/utils.py +17 -0
  153. umap/views.py +7 -18
  154. {umap_project-1.12.2.dist-info → umap_project-1.13.0.dist-info}/METADATA +4 -11
  155. {umap_project-1.12.2.dist-info → umap_project-1.13.0.dist-info}/RECORD +158 -155
  156. {umap_project-1.12.2.dist-info → umap_project-1.13.0.dist-info}/WHEEL +0 -0
  157. {umap_project-1.12.2.dist-info → umap_project-1.13.0.dist-info}/entry_points.txt +0 -0
  158. {umap_project-1.12.2.dist-info → umap_project-1.13.0.dist-info}/licenses/LICENSE +0 -0
@@ -13,7 +13,7 @@ L.U.Browser = L.Class.extend({
13
13
  zoom_to = L.DomUtil.create('i', 'feature-zoom_to', feature_li),
14
14
  edit = L.DomUtil.create('i', 'show-on-edit feature-edit', feature_li),
15
15
  del = L.DomUtil.create('i', 'show-on-edit feature-delete', feature_li),
16
- color = L.DomUtil.create('i', 'feature-color', feature_li),
16
+ colorBox = L.DomUtil.create('i', 'feature-color', feature_li),
17
17
  title = L.DomUtil.create('span', 'feature-title', feature_li),
18
18
  symbol = feature._getIconUrl
19
19
  ? L.U.Icon.prototype.formatUrl(feature._getIconUrl(), feature)
@@ -22,8 +22,12 @@ L.U.Browser = L.Class.extend({
22
22
  edit.title = L._('Edit this feature')
23
23
  del.title = L._('Delete this feature')
24
24
  title.textContent = feature.getDisplayName() || '—'
25
- color.style.backgroundColor = feature.getOption('color')
26
- if (symbol) color.style.backgroundImage = `url(${symbol})`
25
+ const bgcolor = feature.getOption('color')
26
+ colorBox.style.backgroundColor = bgcolor
27
+ if (symbol && symbol !== this.map.options.default_iconUrl) {
28
+ const icon = L.U.Icon.makeIconElement(symbol, colorBox)
29
+ L.U.Icon.setIconContrast(icon, colorBox, symbol, bgcolor)
30
+ }
27
31
  L.DomEvent.on(
28
32
  zoom_to,
29
33
  'click',
@@ -58,6 +62,8 @@ L.U.Browser = L.Class.extend({
58
62
  container.id = `browse_data_datalayer_${datalayer.umap_id}`
59
63
  datalayer.renderToolbox(headline)
60
64
  L.DomUtil.add('span', '', headline, datalayer.options.name)
65
+ const counter = L.DomUtil.add('span', 'datalayer-counter', headline, datalayer.count())
66
+ counter.title = L._('{count} features in this layer', {count: datalayer.count()})
61
67
  const ul = L.DomUtil.create('ul', '', container)
62
68
  L.DomUtil.classIf(container, 'off', !datalayer.isVisible())
63
69
 
@@ -409,9 +409,9 @@ L.Control.Embed = L.Control.extend({
409
409
  const shareButton = L.DomUtil.createButton(
410
410
  '',
411
411
  container,
412
- L._('Share, download and embed this map'),
413
- map.renderShareBox,
414
- map
412
+ L._('Share and download'),
413
+ map.share.open,
414
+ map.share
415
415
  )
416
416
  L.DomEvent.on(shareButton, 'dblclick', L.DomEvent.stopPropagation)
417
417
  return container
@@ -632,12 +632,14 @@ L.U.DataLayersControl = L.Control.extend({
632
632
  function (e) {
633
633
  const layer = this.map.datalayers[e.src.dataset.id],
634
634
  other = this.map.datalayers[e.dst.dataset.id],
635
- minIndex = Math.min(e.initialIndex, e.finalIndex)
635
+ minIndex = Math.min(layer.getRank(), other.getRank()),
636
+ maxIndex = Math.max(layer.getRank(), other.getRank())
636
637
  if (e.finalIndex === 0) layer.bringToTop()
637
638
  else if (e.finalIndex > e.initialIndex) layer.insertBefore(other)
638
639
  else layer.insertAfter(other)
639
640
  this.map.eachDataLayerReverse((datalayer) => {
640
- if (datalayer.getRank() >= minIndex) datalayer.isDirty = true
641
+ if (datalayer.getRank() >= minIndex && datalayer.getRank() <= maxIndex)
642
+ datalayer.isDirty = true
641
643
  })
642
644
  this.map.indexDatalayers()
643
645
  },
@@ -894,46 +896,6 @@ L.U.Map.include({
894
896
  this.ui.openPanel({ data: { html: container }, actions: actions })
895
897
  },
896
898
 
897
- EXPORT_TYPES: {
898
- geojson: {
899
- formatter: function (map) {
900
- return JSON.stringify(map.toGeoJSON(), null, 2)
901
- },
902
- ext: '.geojson',
903
- filetype: 'application/json',
904
- },
905
- gpx: {
906
- formatter: function (map) {
907
- return togpx(map.toGeoJSON())
908
- },
909
- ext: '.gpx',
910
- filetype: 'application/gpx+xml',
911
- },
912
- kml: {
913
- formatter: function (map) {
914
- return tokml(map.toGeoJSON())
915
- },
916
- ext: '.kml',
917
- filetype: 'application/vnd.google-earth.kml+xml',
918
- },
919
- csv: {
920
- formatter: function (map) {
921
- const table = []
922
- map.eachFeature((feature) => {
923
- const row = feature.toGeoJSON()['properties'],
924
- center = feature.getCenter()
925
- delete row['_umap_options']
926
- row['Latitude'] = center.lat
927
- row['Longitude'] = center.lng
928
- table.push(row)
929
- })
930
- return csv2geojson.dsv.csvFormat(table)
931
- },
932
- ext: '.csv',
933
- filetype: 'text/csv',
934
- },
935
- },
936
-
937
899
  renderEditToolbar: function () {
938
900
  const container = L.DomUtil.create(
939
901
  'div',
@@ -1081,107 +1043,6 @@ L.U.Map.include({
1081
1043
  this
1082
1044
  )
1083
1045
  },
1084
-
1085
- renderShareBox: function () {
1086
- const container = L.DomUtil.create('div', 'umap-share')
1087
- const title = L.DomUtil.create('h3', '', container)
1088
- title.textContent = L._('Share, download and embed this map')
1089
-
1090
- const embedTitle = L.DomUtil.add('h4', '', container, L._('Embed the map'))
1091
- const iframe = L.DomUtil.create('textarea', 'umap-share-iframe', container)
1092
- const urlTitle = L.DomUtil.add('h4', '', container, L._('Direct link'))
1093
- const exportUrl = L.DomUtil.create('input', 'umap-share-url', container)
1094
- let option
1095
- exportUrl.type = 'text'
1096
- const UIFields = [
1097
- ['dimensions.width', { handler: 'Input', label: L._('width') }],
1098
- ['dimensions.height', { handler: 'Input', label: L._('height') }],
1099
- [
1100
- 'options.includeFullScreenLink',
1101
- { handler: 'Switch', label: L._('Include full screen link?') },
1102
- ],
1103
- [
1104
- 'options.currentView',
1105
- { handler: 'Switch', label: L._('Current view instead of default map view?') },
1106
- ],
1107
- [
1108
- 'options.keepCurrentDatalayers',
1109
- { handler: 'Switch', label: L._('Keep current visible layers') },
1110
- ],
1111
- [
1112
- 'options.viewCurrentFeature',
1113
- { handler: 'Switch', label: L._('Open current feature on load') },
1114
- ],
1115
- 'queryString.moreControl',
1116
- 'queryString.scrollWheelZoom',
1117
- 'queryString.miniMap',
1118
- 'queryString.scaleControl',
1119
- 'queryString.onLoadPanel',
1120
- 'queryString.captionBar',
1121
- 'queryString.captionMenus',
1122
- ]
1123
- for (let i = 0; i < this.HIDDABLE_CONTROLS.length; i++) {
1124
- UIFields.push(`queryString.${this.HIDDABLE_CONTROLS[i]}Control`)
1125
- }
1126
- const iframeExporter = new L.U.IframeExporter(this)
1127
- const buildIframeCode = () => {
1128
- iframe.innerHTML = iframeExporter.build()
1129
- exportUrl.value = window.location.protocol + iframeExporter.buildUrl()
1130
- }
1131
- buildIframeCode()
1132
- const builder = new L.U.FormBuilder(iframeExporter, UIFields, {
1133
- callback: buildIframeCode,
1134
- })
1135
- const iframeOptions = L.DomUtil.createFieldset(container, L._('Export options'))
1136
- iframeOptions.appendChild(builder.build())
1137
- if (this.options.shortUrl) {
1138
- L.DomUtil.create('hr', '', container)
1139
- L.DomUtil.add('h4', '', container, L._('Short URL'))
1140
- const shortUrl = L.DomUtil.create('input', 'umap-short-url', container)
1141
- shortUrl.type = 'text'
1142
- shortUrl.value = this.options.shortUrl
1143
- }
1144
- L.DomUtil.create('hr', '', container)
1145
- L.DomUtil.add('h4', '', container, L._('Backup data'))
1146
- const downloadUrl = L.Util.template(this.options.urls.map_download, {
1147
- map_id: this.options.umap_id,
1148
- })
1149
- const link = L.DomUtil.createLink(
1150
- 'button',
1151
- container,
1152
- L._('Download full data'),
1153
- downloadUrl
1154
- )
1155
- let name = this.options.name || 'data'
1156
- name = name.replace(/[^a-z0-9]/gi, '_').toLowerCase()
1157
- link.setAttribute('download', `${name}.umap`)
1158
- L.DomUtil.create('hr', '', container)
1159
- L.DomUtil.add('h4', '', container, L._('Download data'))
1160
- const typeInput = L.DomUtil.create('select', '', container)
1161
- typeInput.name = 'format'
1162
- const exportCaveat = L.DomUtil.add(
1163
- 'small',
1164
- 'help-text',
1165
- container,
1166
- L._('Only visible features will be downloaded.')
1167
- )
1168
- for (const key in this.EXPORT_TYPES) {
1169
- if (this.EXPORT_TYPES.hasOwnProperty(key)) {
1170
- option = L.DomUtil.create('option', '', typeInput)
1171
- option.value = key
1172
- option.textContent = this.EXPORT_TYPES[key].name || key
1173
- if (this.EXPORT_TYPES[key].selected) option.selected = true
1174
- }
1175
- }
1176
- L.DomUtil.createButton(
1177
- 'button',
1178
- container,
1179
- L._('Download data'),
1180
- () => this.download(typeInput.value),
1181
- this
1182
- )
1183
- this.ui.openPanel({ data: { html: container } })
1184
- },
1185
1046
  })
1186
1047
 
1187
1048
  /* Used in view mode to define the current tilelayer */
@@ -1363,9 +1224,47 @@ L.U.StarControl = L.Control.extend({
1363
1224
 
1364
1225
  L.U.Search = L.PhotonSearch.extend({
1365
1226
  initialize: function (map, input, options) {
1227
+ this.options.placeholder = L._('Type a place name or coordinates')
1366
1228
  L.PhotonSearch.prototype.initialize.call(this, map, input, options)
1367
1229
  this.options.url = map.options.urls.search
1368
1230
  if (map.options.maxBounds) this.options.bbox = map.options.maxBounds.toBBoxString()
1231
+ this.reverse = new L.PhotonReverse({
1232
+ handleResults: (geojson) => {
1233
+ this.handleResultsWithReverse(geojson)
1234
+ },
1235
+ })
1236
+ },
1237
+
1238
+ handleResultsWithReverse: function (geojson) {
1239
+ const latlng = this.reverse.latlng
1240
+ geojson.features.unshift({
1241
+ type: 'Feature',
1242
+ geometry: { type: 'Point', coordinates: [latlng.lng, latlng.lat] },
1243
+ properties: {
1244
+ name: L._('Go to "{coords}"', { coords: `${latlng.lat} ${latlng.lng}` }),
1245
+ },
1246
+ })
1247
+
1248
+ this.handleResults(geojson)
1249
+ },
1250
+
1251
+ search: function () {
1252
+ const pattern = /^(?<lat>[-+]?\d{1,2}[.,]\d+)\s*[ ,]\s*(?<lng>[-+]?\d{1,3}[.,]\d+)$/
1253
+ if (pattern.test(this.input.value)) {
1254
+ this.hide()
1255
+ const { lat, lng } = pattern.exec(this.input.value).groups
1256
+ const latlng = L.latLng(lat, lng)
1257
+ if (latlng.isValid()) {
1258
+ this.reverse.doReverse(latlng)
1259
+ } else {
1260
+ this.map.ui.alert({ content: 'Invalid latitude or longitude', mode: 'error' })
1261
+ }
1262
+ return
1263
+ }
1264
+ // Only numbers, abort.
1265
+ if (/^[\d .,]*$/.test(this.input.value)) return
1266
+ // Do normal search
1267
+ L.PhotonSearch.prototype.search.call(this)
1369
1268
  },
1370
1269
 
1371
1270
  onBlur: function (e) {
@@ -1513,77 +1412,6 @@ L.U.ContextMenu = L.Map.ContextMenu.extend({
1513
1412
  },
1514
1413
  })
1515
1414
 
1516
- L.U.IframeExporter = L.Evented.extend({
1517
- options: {
1518
- includeFullScreenLink: true,
1519
- currentView: false,
1520
- keepCurrentDatalayers: false,
1521
- viewCurrentFeature: false,
1522
- },
1523
-
1524
- queryString: {
1525
- scaleControl: false,
1526
- miniMap: false,
1527
- scrollWheelZoom: false,
1528
- zoomControl: true,
1529
- editMode: 'disabled',
1530
- moreControl: true,
1531
- searchControl: null,
1532
- tilelayersControl: null,
1533
- embedControl: null,
1534
- datalayersControl: true,
1535
- onLoadPanel: 'none',
1536
- captionBar: false,
1537
- captionMenus: true,
1538
- },
1539
-
1540
- dimensions: {
1541
- width: '100%',
1542
- height: '300px',
1543
- },
1544
-
1545
- initialize: function (map) {
1546
- this.map = map
1547
- this.baseUrl = L.Util.getBaseUrl()
1548
- // Use map default, not generic default
1549
- this.queryString.onLoadPanel = this.map.options.onLoadPanel
1550
- },
1551
-
1552
- getMap: function () {
1553
- return this.map
1554
- },
1555
-
1556
- buildUrl: function (options) {
1557
- const datalayers = []
1558
- if (this.options.viewCurrentFeature && this.map.currentFeature) {
1559
- this.queryString.feature = this.map.currentFeature.getSlug()
1560
- }
1561
- if (this.options.keepCurrentDatalayers) {
1562
- this.map.eachDataLayer((datalayer) => {
1563
- if (datalayer.isVisible() && datalayer.umap_id) {
1564
- datalayers.push(datalayer.umap_id)
1565
- }
1566
- })
1567
- this.queryString.datalayers = datalayers.join(',')
1568
- } else {
1569
- delete this.queryString.datalayers
1570
- }
1571
- const currentView = this.options.currentView ? window.location.hash : ''
1572
- const queryString = L.extend({}, this.queryString, options)
1573
- return `${this.baseUrl}?${L.Util.buildQueryString(queryString)}${currentView}`
1574
- },
1575
-
1576
- build: function () {
1577
- const iframeUrl = this.buildUrl()
1578
- let code = `<iframe width="${this.dimensions.width}" height="${this.dimensions.height}" frameborder="0" allowfullscreen allow="geolocation" src="${iframeUrl}"></iframe>`
1579
- if (this.options.includeFullScreenLink) {
1580
- const fullUrl = this.buildUrl({ scrollWheelZoom: true })
1581
- code += `<p><a href="${fullUrl}">${L._('See full screen')}</a></p>`
1582
- }
1583
- return code
1584
- },
1585
- })
1586
-
1587
1415
  L.U.Editable = L.Editable.extend({
1588
1416
  initialize: function (map, options) {
1589
1417
  L.Editable.prototype.initialize.call(this, map, options)
@@ -262,6 +262,18 @@ L.Util.hasVar = (value) => {
262
262
  return typeof value === 'string' && value.indexOf('{') != -1
263
263
  }
264
264
 
265
+ L.Util.isPath = function (value) {
266
+ return value && value.length && value.startsWith('/')
267
+ }
268
+
269
+ L.Util.isRemoteUrl = function (value) {
270
+ return value && value.length && value.startsWith('http')
271
+ }
272
+
273
+ L.Util.isDataImage = function (value) {
274
+ return value && value.length && value.startsWith('data:image')
275
+ }
276
+
265
277
  L.Util.copyToClipboard = function (textToCopy) {
266
278
  // https://stackoverflow.com/a/65996386
267
279
  // Navigator clipboard api needs a secure context (https)
@@ -346,6 +358,24 @@ L.DomUtil.createLink = (className, container, content, url, target, title) => {
346
358
  return el
347
359
  }
348
360
 
361
+ L.DomUtil.createCopiableInput = (parent, label, value) => {
362
+ const wrapper = L.DomUtil.add('div', 'copiable-input', parent)
363
+ const labelEl = L.DomUtil.add('label', '', wrapper, label)
364
+ const input = L.DomUtil.add('input', '', labelEl)
365
+ input.type = 'text'
366
+ input.readOnly = true
367
+ input.value = value
368
+ const button = L.DomUtil.createButton(
369
+ '',
370
+ wrapper,
371
+ '',
372
+ () => L.Util.copyToClipboard(input.value),
373
+ this
374
+ )
375
+ button.title = L._('copy')
376
+ return input
377
+ }
378
+
349
379
  L.DomUtil.classIf = (el, className, bool) => {
350
380
  if (bool) L.DomUtil.addClass(el, className)
351
381
  else L.DomUtil.removeClass(el, className)
@@ -373,8 +403,8 @@ L.DomUtil.after = (target, el) => {
373
403
  }
374
404
 
375
405
  L.DomUtil.RGBRegex = /rgb *\( *([0-9]{1,3}) *, *([0-9]{1,3}) *, *([0-9]{1,3}) *\)/
376
- L.DomUtil.TextColorFromBackgroundColor = (el) => {
377
- return L.DomUtil.contrastedColor(el) ? '#ffffff' : '#000000'
406
+ L.DomUtil.TextColorFromBackgroundColor = (el, bgcolor) => {
407
+ return L.DomUtil.contrastedColor(el, bgcolor) ? '#ffffff' : '#000000'
378
408
  }
379
409
  const _CACHE_CONSTRAST = {}
380
410
  L.DomUtil.contrastedColor = (el, bgcolor) => {
@@ -436,60 +466,62 @@ L.U.Keys = {
436
466
  }
437
467
 
438
468
  L.U.Help = L.Class.extend({
439
-
440
469
  SHORTCUTS: {
441
470
  DRAW_MARKER: {
442
- shortcut:'Modifier+M',
443
- label: L._('Draw a marker')
471
+ shortcut: 'Modifier+M',
472
+ label: L._('Draw a marker'),
444
473
  },
445
474
  DRAW_LINE: {
446
- shortcut:'Modifier+L',
447
- label: L._('Draw a polyline')
475
+ shortcut: 'Modifier+L',
476
+ label: L._('Draw a polyline'),
448
477
  },
449
478
  DRAW_POLYGON: {
450
- shortcut:'Modifier+P',
451
- label: L._('Draw a polygon')
479
+ shortcut: 'Modifier+P',
480
+ label: L._('Draw a polygon'),
452
481
  },
453
482
  TOGGLE_EDIT: {
454
- shortcut:'Modifier+E',
455
- label: L._('Toggle edit mode')
483
+ shortcut: 'Modifier+E',
484
+ label: L._('Toggle edit mode'),
456
485
  },
457
486
  STOP_EDIT: {
458
- shortcut:'Modifier+E',
459
- label: L._('Stop editing')
487
+ shortcut: 'Modifier+E',
488
+ label: L._('Stop editing'),
460
489
  },
461
490
  SAVE_MAP: {
462
- shortcut:'Modifier+S',
463
- label: L._('Save map')
491
+ shortcut: 'Modifier+S',
492
+ label: L._('Save map'),
464
493
  },
465
494
  IMPORT_PANEL: {
466
- shortcut:'Modifier+I',
467
- label: L._('Import data')
495
+ shortcut: 'Modifier+I',
496
+ label: L._('Import data'),
468
497
  },
469
498
  SEARCH: {
470
- shortcut:'Modifier+F',
471
- label: L._('Search location')
499
+ shortcut: 'Modifier+F',
500
+ label: L._('Search location'),
472
501
  },
473
502
  CANCEL: {
474
- shortcut:'Modifier+Z',
475
- label: L._('Cancel edits')
503
+ shortcut: 'Modifier+Z',
504
+ label: L._('Cancel edits'),
476
505
  },
477
506
  PREVIEW: {
478
- shortcut:'Modifier+E',
479
- label: L._('Back to preview')
507
+ shortcut: 'Modifier+E',
508
+ label: L._('Back to preview'),
480
509
  },
481
510
  SAVE: {
482
- shortcut:'Modifier+S',
483
- label: L._('Save current edits')
511
+ shortcut: 'Modifier+S',
512
+ label: L._('Save current edits'),
484
513
  },
485
514
  },
486
515
 
487
- displayLabel: function (action, withKbdTag=true) {
488
- let {shortcut, label} = this.SHORTCUTS[action]
516
+ displayLabel: function (action, withKbdTag = true) {
517
+ let { shortcut, label } = this.SHORTCUTS[action]
489
518
  const modifier = this.isMacOS ? 'Cmd' : 'Ctrl'
490
519
  shortcut = shortcut.replace('Modifier', modifier)
491
520
  if (withKbdTag) {
492
- shortcut = shortcut.split('+').map((el) => `<kbd>${el}</kbd>`).join('+')
521
+ shortcut = shortcut
522
+ .split('+')
523
+ .map((el) => `<kbd>${el}</kbd>`)
524
+ .join('+')
493
525
  label += ` ${shortcut}`
494
526
  } else {
495
527
  label += ` (${shortcut})`
@@ -515,7 +547,9 @@ L.U.Help = L.Class.extend({
515
547
  const label = L.DomUtil.create('span', '', closeButton)
516
548
  label.title = label.textContent = L._('Close')
517
549
  this.content = L.DomUtil.create('div', 'umap-help-content', this.box)
518
- this.isMacOS = /mac/i.test(navigator.userAgentData ? navigator.userAgentData.platform : navigator.platform)
550
+ this.isMacOS = /mac/i.test(
551
+ navigator.userAgentData ? navigator.userAgentData.platform : navigator.platform
552
+ )
519
553
  },
520
554
 
521
555
  onKeyDown: function (e) {
@@ -708,7 +742,9 @@ L.U.Help = L.Class.extend({
708
742
  facetKey: L._(
709
743
  'Comma separated list of properties to use for facet search (eg.: mykey,otherkey). To control label, add it after a | (eg.: mykey|My Key,otherkey|Other Key)'
710
744
  ),
711
- interactive: L._('If false, the polygon or line will act as a part of the underlying map.'),
745
+ interactive: L._(
746
+ 'If false, the polygon or line will act as a part of the underlying map.'
747
+ ),
712
748
  outlink: L._('Define link to open in a new window on polygon click.'),
713
749
  dynamicRemoteData: L._('Fetch data each time map view changes.'),
714
750
  proxyRemoteData: L._("To use if remote server doesn't allow cross domain (slower)"),
@@ -372,16 +372,20 @@ L.FormBuilder.PopupContent = L.FormBuilder.Select.extend({
372
372
  ['Table', L._('Table')],
373
373
  ['GeoRSSImage', L._('GeoRSS (title + image)')],
374
374
  ['GeoRSSLink', L._('GeoRSS (only link)')],
375
+ ['OSM', L._('OpenStreetMap')],
375
376
  ],
376
377
  })
377
378
 
378
379
  L.FormBuilder.LayerTypeChooser = L.FormBuilder.Select.extend({
379
- selectOptions: [
380
- ['Default', L._('Default')],
381
- ['Cluster', L._('Clustered')],
382
- ['Heat', L._('Heatmap')],
383
- ['Choropleth', L._('Choropleth')],
384
- ],
380
+ getOptions: function () {
381
+ const layer_classes = [
382
+ L.U.Layer.Default,
383
+ L.U.Layer.Cluster,
384
+ L.U.Layer.Heat,
385
+ L.U.Layer.Choropleth,
386
+ ]
387
+ return layer_classes.map((class_) => [class_.TYPE, class_.NAME])
388
+ },
385
389
  })
386
390
 
387
391
  L.FormBuilder.SlideshowDelay = L.FormBuilder.IntSelect.extend({
@@ -538,8 +542,8 @@ L.FormBuilder.IconUrl = L.FormBuilder.BlurInput.extend({
538
542
  this.footer.innerHTML = ''
539
543
  this.buildTabs()
540
544
  const value = this.value()
541
- if (!value || value.startsWith('/')) this.showSymbolsTab()
542
- else if (value.startsWith('http')) this.showURLTab()
545
+ if (!value || L.Util.isPath(value)) this.showSymbolsTab()
546
+ else if (L.Util.isRemoteUrl(value) || L.Util.isDataImage(value)) this.showURLTab()
543
547
  else this.showCharsTab()
544
548
  const closeButton = L.DomUtil.createButton(
545
549
  'button action-button',
@@ -596,20 +600,6 @@ L.FormBuilder.IconUrl = L.FormBuilder.BlurInput.extend({
596
600
  this.body.innerHTML = ''
597
601
  },
598
602
 
599
- isPath: function () {
600
- const value = this.value()
601
- return value && value.length && value.startsWith('/')
602
- },
603
-
604
- isRemoteUrl: function () {
605
- const value = this.value()
606
- return value && value.length && value.startsWith('http')
607
- },
608
-
609
- isImg: function () {
610
- return this.isPath() || this.isRemoteUrl()
611
- },
612
-
613
603
  updatePreview: function () {
614
604
  this.buttons.innerHTML = ''
615
605
  if (this.isDefault()) return
@@ -617,13 +607,7 @@ L.FormBuilder.IconUrl = L.FormBuilder.BlurInput.extend({
617
607
  // Do not try to render URL with variables
618
608
  const box = L.DomUtil.create('div', 'umap-pictogram-choice', this.buttons)
619
609
  L.DomEvent.on(box, 'click', this.onDefine, this)
620
- if (this.isImg()) {
621
- const img = L.DomUtil.create('img', '', box)
622
- img.src = this.value()
623
- } else {
624
- const el = L.DomUtil.create('span', '', box)
625
- el.textContent = this.value()
626
- }
610
+ const icon = L.U.Icon.makeIconElement(this.value(), box)
627
611
  }
628
612
  this.button = L.DomUtil.createButton(
629
613
  'button action-button',
@@ -723,7 +707,7 @@ L.FormBuilder.IconUrl = L.FormBuilder.BlurInput.extend({
723
707
 
724
708
  showCharsTab: function () {
725
709
  this.openTab('chars')
726
- const value = !this.isImg() ? this.value() : null
710
+ const value = !L.U.Icon.isImg(this.value()) ? this.value() : null
727
711
  const input = this.buildInput(this.body, value)
728
712
  input.placeholder = L._('Type char or paste emoji')
729
713
  input.type = 'text'
@@ -731,7 +715,10 @@ L.FormBuilder.IconUrl = L.FormBuilder.BlurInput.extend({
731
715
 
732
716
  showURLTab: function () {
733
717
  this.openTab('url')
734
- const value = this.isRemoteUrl() ? this.value() : null
718
+ const value =
719
+ L.Util.isRemoteUrl(this.value()) || L.Util.isDataImage(this.value())
720
+ ? this.value()
721
+ : null
735
722
  const input = this.buildInput(this.body, value)
736
723
  input.placeholder = L._('Add image URL')
737
724
  input.type = 'url'
@@ -67,15 +67,8 @@ L.U.Icon.Default = L.U.Icon.extend({
67
67
 
68
68
  onAdd: function () {
69
69
  const src = this._getIconUrl('icon')
70
- // Decide whether to switch svg to white or not, but do it
71
- // only for internal SVG, as invert could do weird things
72
- if (src.startsWith('/') && src.endsWith('.svg')) {
73
- const bgcolor = this._getColor()
74
- // Must be called after icon container is added to the DOM
75
- if (L.DomUtil.contrastedColor(this.elements.container, bgcolor)) {
76
- this.elements.img.style.filter = 'invert(1)'
77
- }
78
- }
70
+ const bgcolor = this._getColor()
71
+ L.U.Icon.setIconContrast(this.elements.icon, this.elements.container, src, bgcolor)
79
72
  },
80
73
 
81
74
  createIcon: function () {
@@ -89,18 +82,7 @@ L.U.Icon.Default = L.U.Icon.extend({
89
82
  this.elements.arrow = L.DomUtil.create('div', 'icon_arrow', this.elements.main)
90
83
  const src = this._getIconUrl('icon')
91
84
  if (src) {
92
- // An url.
93
- if (
94
- src.startsWith('http') ||
95
- src.startsWith('/') ||
96
- src.startsWith('data:image')
97
- ) {
98
- this.elements.img = L.DomUtil.create('img', null, this.elements.container)
99
- this.elements.img.src = src
100
- } else {
101
- this.elements.span = L.DomUtil.create('span', null, this.elements.container)
102
- this.elements.span.textContent = src
103
- }
85
+ this.elements.icon = L.U.Icon.makeIconElement(src, this.elements.container)
104
86
  }
105
87
  this._setIconStyles(this.elements.main, 'icon')
106
88
  return this.elements.main
@@ -208,3 +190,44 @@ L.U.Icon.Cluster = L.DivIcon.extend({
208
190
  return color || L.DomUtil.TextColorFromBackgroundColor(el, backgroundColor)
209
191
  },
210
192
  })
193
+
194
+ L.U.Icon.isImg = function (src) {
195
+ return L.Util.isPath(src) || L.Util.isRemoteUrl(src) || L.Util.isDataImage(src)
196
+ }
197
+
198
+ L.U.Icon.makeIconElement = function (src, parent) {
199
+ let icon
200
+ if (L.U.Icon.isImg(src)) {
201
+ icon = L.DomUtil.create('img')
202
+ icon.src = src
203
+ } else {
204
+ icon = L.DomUtil.create('span')
205
+ icon.textContent = src
206
+ }
207
+ parent.appendChild(icon)
208
+ return icon
209
+ }
210
+
211
+ L.U.Icon.setIconContrast = function (el, parent, src, bgcolor) {
212
+ /*
213
+ * el: the element we'll adapt the style, it can be an image or text
214
+ * parent: the element we'll consider to decide whether to adapt the style,
215
+ * by looking at its background color
216
+ * src: the raw "icon" value, can be an URL, a path, text, emoticon, etc.
217
+ * bgcolor: the background color, used for caching and in case we cannot guess the
218
+ * parent background color
219
+ */
220
+
221
+ if (L.DomUtil.contrastedColor(parent, bgcolor)) {
222
+ // Decide whether to switch svg to white or not, but do it
223
+ // only for internal SVG, as invert could do weird things
224
+ if (L.Util.isPath(src) && src.endsWith('.svg')) {
225
+ // Must be called after icon container is added to the DOM
226
+ // An image
227
+ el.style.filter = 'invert(1)'
228
+ } else if (!el.src) {
229
+ // Text icon
230
+ el.style.color = 'white'
231
+ }
232
+ }
233
+ }