qwc2 2026.4.3 → 2026.4.20

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 (49) hide show
  1. package/actions/theme.js +6 -5
  2. package/components/AppMenu.js +2 -1
  3. package/components/IdentifyViewer.js +5 -0
  4. package/components/SearchBox.js +10 -2
  5. package/components/StandardApp.js +3 -7
  6. package/components/map3d/layers/MVTLayer3D.js +1 -2
  7. package/components/style/PluginsContainer.css +3 -7
  8. package/components/style/SearchBox.css +11 -0
  9. package/components/widgets/LayerCatalogWidget.js +19 -1
  10. package/components/widgets/style/LayerCatalogWidget.css +11 -0
  11. package/package.json +3 -3
  12. package/plugins/API.js +23 -0
  13. package/plugins/Editing.js +15 -4
  14. package/plugins/MapExport.js +59 -36
  15. package/plugins/MapInfoTooltip.js +3 -2
  16. package/plugins/NewsPopup.js +0 -1
  17. package/plugins/Print.js +26 -10
  18. package/plugins/SensorThingsTool.js +2 -2
  19. package/plugins/map/MeasurementSupport.js +19 -7
  20. package/plugins/style/Authentication.css +4 -0
  21. package/plugins/style/LayerTree.css +5 -0
  22. package/plugins/style/ThemeSwitcher.css +4 -0
  23. package/scripts/gen-plugin-docs.js +2 -0
  24. package/scripts/makeIconkit.js +6 -8
  25. package/static/translations/bg-BG.json +2 -3
  26. package/static/translations/ca-ES.json +2 -3
  27. package/static/translations/cs-CZ.json +2 -3
  28. package/static/translations/de-CH.json +3 -4
  29. package/static/translations/de-DE.json +3 -4
  30. package/static/translations/en-US.json +3 -4
  31. package/static/translations/es-ES.json +2 -3
  32. package/static/translations/fi-FI.json +2 -3
  33. package/static/translations/fr-FR.json +90 -91
  34. package/static/translations/hu-HU.json +2 -3
  35. package/static/translations/it-IT.json +92 -93
  36. package/static/translations/ja-JP.json +2 -3
  37. package/static/translations/nl-NL.json +2 -3
  38. package/static/translations/no-NO.json +2 -3
  39. package/static/translations/pl-PL.json +2 -3
  40. package/static/translations/pt-BR.json +2 -3
  41. package/static/translations/pt-PT.json +2 -3
  42. package/static/translations/ro-RO.json +2 -3
  43. package/static/translations/ru-RU.json +2 -3
  44. package/static/translations/sv-SE.json +2 -3
  45. package/static/translations/tr-TR.json +2 -3
  46. package/static/translations/tsconfig.json +1 -2
  47. package/static/translations/uk-UA.json +2 -3
  48. package/utils/PermaLinkUtils.js +1 -1
  49. package/utils/ThemeUtils.js +2 -1
package/actions/theme.js CHANGED
@@ -52,7 +52,7 @@ export function setThemeLayersList(theme) {
52
52
  themelist: theme
53
53
  };
54
54
  }
55
- export function finishThemeSetup(dispatch, theme, themes, layerConfigs, insertPos, permalinkLayers, externalLayerRestorer, visibleBgLayer, initialTheme) {
55
+ export function finishThemeSetup(dispatch, theme, themes, layerConfigs, insertPos, permalinkLayers, externalLayerRestorer, visibleBgLayer, initialTheme, initialTask) {
56
56
  var _theme$config;
57
57
  // Create layer
58
58
  var themeLayer = ThemeUtils.createThemeLayer(theme, themes);
@@ -148,10 +148,10 @@ export function finishThemeSetup(dispatch, theme, themes, layerConfigs, insertPo
148
148
  type: SWITCHING_THEME,
149
149
  switching: false
150
150
  });
151
- var task = (_theme$config = theme.config) === null || _theme$config === void 0 ? void 0 : _theme$config.startupTask;
151
+ var task = initialTask || (theme === null || theme === void 0 || (_theme$config = theme.config) === null || _theme$config === void 0 ? void 0 : _theme$config.startupTask) || (initialTheme ? ConfigUtils.getConfigProp("startupTask") : null);
152
152
  if (task) {
153
153
  var mapClickAction = ConfigUtils.getPluginConfig(task.key).mapClickAction;
154
- dispatch(setCurrentTask(task.key, task.mode, mapClickAction));
154
+ dispatch(setCurrentTask(task.key, task.mode, mapClickAction, task.data));
155
155
  }
156
156
  }
157
157
  export function setCurrentTheme(theme, themes) {
@@ -162,6 +162,7 @@ export function setCurrentTheme(theme, themes) {
162
162
  var permalinkLayers = arguments.length > 6 && arguments[6] !== undefined ? arguments[6] : null;
163
163
  var themeLayerRestorer = arguments.length > 7 && arguments[7] !== undefined ? arguments[7] : null;
164
164
  var externalLayerRestorer = arguments.length > 8 && arguments[8] !== undefined ? arguments[8] : null;
165
+ var initialTask = arguments.length > 9 && arguments[9] !== undefined ? arguments[9] : null;
165
166
  return function (dispatch, getState) {
166
167
  var _getState$layers, _ref, _theme$mapTips;
167
168
  var curLayers = ((_getState$layers = getState().layers) === null || _getState$layers === void 0 ? void 0 : _getState$layers.flat) || [];
@@ -318,13 +319,13 @@ export function setCurrentTheme(theme, themes) {
318
319
  dispatch(showNotification("missinglayers", LocaleUtils.tr("app.missinglayers", diff.join(", ")), NotificationType.WARN, true));
319
320
  }
320
321
  }
321
- finishThemeSetup(dispatch, newTheme, themes, layerConfigs, insertPos, permalinkLayers, externalLayerRestorer, visibleBgLayer, initialTheme);
322
+ finishThemeSetup(dispatch, newTheme, themes, layerConfigs, insertPos, permalinkLayers, externalLayerRestorer, visibleBgLayer, initialTheme, initialTask);
322
323
  });
323
324
  } else {
324
325
  if (!isEmpty(missingThemeLayers)) {
325
326
  dispatch(showNotification("missinglayers", LocaleUtils.tr("app.missinglayers", Object.keys(missingThemeLayers).join(", ")), NotificationType.WARN, true));
326
327
  }
327
- finishThemeSetup(dispatch, theme, themes, layerConfigs, insertPos, permalinkLayers, externalLayerRestorer, visibleBgLayer, initialTheme);
328
+ finishThemeSetup(dispatch, theme, themes, layerConfigs, insertPos, permalinkLayers, externalLayerRestorer, visibleBgLayer, initialTheme, initialTask);
328
329
  }
329
330
  };
330
331
  }
@@ -373,7 +373,8 @@ _defineProperty(AppMenu, "propTypes", {
373
373
  showOnStartup: PropTypes.bool
374
374
  });
375
375
  _defineProperty(AppMenu, "defaultProps", {
376
- onMenuToggled: function onMenuToggled() {}
376
+ onMenuToggled: function onMenuToggled() {},
377
+ menuDisplayMode: "normal"
377
378
  });
378
379
  export default connect(function (state) {
379
380
  return {
@@ -1151,6 +1151,11 @@ var IdentifyViewer = /*#__PURE__*/function (_React$Component) {
1151
1151
  }));
1152
1152
  }
1153
1153
  text = "" + text; // Ensure text is a string
1154
+ for (var _i = 0, _Object$values = Object.values(((_window$qwc3 = window.qwc2) === null || _window$qwc3 === void 0 ? void 0 : _window$qwc3.__attributeFormatters) || {}); _i < _Object$values.length; _i++) {
1155
+ var _window$qwc3;
1156
+ var formatter = _Object$values[_i];
1157
+ text = formatter(attrName, text, layer, result);
1158
+ }
1154
1159
  text = _this.props.attributeTransform(attrName, text, layer, result);
1155
1160
  text = MiscUtils.addLinkAnchors(text);
1156
1161
  return _this.parsedContent(text);
@@ -490,6 +490,14 @@ var SearchBox = /*#__PURE__*/function (_React$Component) {
490
490
  onClick: function onClick(ev) {
491
491
  return _this.toggleLayerInfo(ev, provider, group, result, key, parent);
492
492
  }
493
+ }) : null, result.layer.link ? /*#__PURE__*/React.createElement(Icon, {
494
+ className: "searchbox-entry-link",
495
+ icon: "info-sign",
496
+ onClick: function onClick(ev) {
497
+ var _result$label4;
498
+ return _this.openUrl(ev, result.layer.link, result.target, (_result$label4 = result.label) !== null && _result$label4 !== void 0 ? _result$label4 : result.text);
499
+ },
500
+ title: LocaleUtils.tr("layercatalog.openlink")
493
501
  }) : null), _this.state.activeLayerInfo === key ? /*#__PURE__*/React.createElement("div", {
494
502
  className: "searchbox-result-abstract",
495
503
  dangerouslySetInnerHTML: {
@@ -505,7 +513,7 @@ var SearchBox = /*#__PURE__*/function (_React$Component) {
505
513
  }) : null].flat();
506
514
  });
507
515
  _defineProperty(_this, "renderThemeResult", function (provider, group, result) {
508
- var _result$label4;
516
+ var _result$label5;
509
517
  var addThemes = ConfigUtils.getConfigProp("allowAddingOtherThemes", _this.props.theme);
510
518
  return /*#__PURE__*/React.createElement("div", {
511
519
  className: "searchbox-result",
@@ -527,7 +535,7 @@ var SearchBox = /*#__PURE__*/function (_React$Component) {
527
535
  dangerouslySetInnerHTML: {
528
536
  __html: DOMPurify.sanitize(result.text).replace(/<br\s*\/>/ig, ' ')
529
537
  },
530
- title: (_result$label4 = result.label) !== null && _result$label4 !== void 0 ? _result$label4 : result.text
538
+ title: (_result$label5 = result.label) !== null && _result$label5 !== void 0 ? _result$label5 : result.text
531
539
  }), result.theme && addThemes ? /*#__PURE__*/React.createElement(Icon, {
532
540
  icon: "plus",
533
541
  onClick: function onClick(ev) {
@@ -92,7 +92,7 @@ var AppContainerComponent = /*#__PURE__*/function (_React$Component) {
92
92
  lang: _this.props.locale
93
93
  }
94
94
  }).then(function (response) {
95
- var _this$props$appConfig, _this$props$appConfig2, _theme;
95
+ var _this$props$appConfig, _this$props$appConfig2;
96
96
  var themes = response.data.themes || {};
97
97
  (_this$props$appConfig = (_this$props$appConfig2 = _this.props.appConfig).themePreprocessor) === null || _this$props$appConfig === void 0 || _this$props$appConfig.call(_this$props$appConfig2, themes);
98
98
  _this.props.themesLoaded(themes);
@@ -162,15 +162,11 @@ var AppContainerComponent = /*#__PURE__*/function (_React$Component) {
162
162
  if (layerParams && ConfigUtils.getConfigProp("urlReverseLayerOrder")) {
163
163
  layerParams.reverse();
164
164
  }
165
- _this.props.setCurrentTheme(theme, themes, false, initialExtent, layerParams, (_params$bl = params.bl) !== null && _params$bl !== void 0 ? _params$bl : null, state.layers, _this.props.appConfig.themeLayerRestorer, _this.props.appConfig.externalLayerRestorer);
165
+ var initialTaskParam = params.task ? JSON.parse(decodeURIComponent(params.task)) : null;
166
+ _this.props.setCurrentTheme(theme, themes, false, initialExtent, layerParams, (_params$bl = params.bl) !== null && _params$bl !== void 0 ? _params$bl : null, state.layers, _this.props.appConfig.themeLayerRestorer, _this.props.appConfig.externalLayerRestorer, initialTaskParam);
166
167
  } else if (!ConfigUtils.havePlugin("Portal")) {
167
168
  _this.props.showNotification("missingdefaulttheme", LocaleUtils.tr("app.missingdefaulttheme", params.t), NotificationType.WARN, true);
168
169
  }
169
- var task = ConfigUtils.getConfigProp("startupTask");
170
- if (task && !((_theme = theme) !== null && _theme !== void 0 && (_theme = _theme.config) !== null && _theme !== void 0 && _theme.startupTask)) {
171
- var mapClickAction = ConfigUtils.getPluginConfig(task.key).mapClickAction;
172
- _this.props.setCurrentTask(task.key, task.mode, mapClickAction);
173
- }
174
170
  });
175
171
  });
176
172
  _this.themesLoaded = false;
@@ -16,8 +16,7 @@ export default {
16
16
  var style = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
17
17
  var source = new VectorTileSource({
18
18
  url: url,
19
- style: style,
20
- backgroundColor: "white"
19
+ style: style
21
20
  });
22
21
  return new ColorLayer({
23
22
  name: options.name,
@@ -21,7 +21,7 @@ div.app-infos-container {
21
21
  flex-wrap: wrap-reverse;
22
22
  z-index: 2;
23
23
  overflow: hidden;
24
- padding: 1em 1em 2.5em 1em;
24
+ padding: .5em .5em 2.5em .5em;
25
25
  row-gap: 0.25em;
26
26
  }
27
27
 
@@ -58,13 +58,9 @@ div.app-info {
58
58
  color: var(--panel-text-color);
59
59
  background-color: var(--panel-bg-color);
60
60
  box-shadow: 0 0 4px rgba(136, 136, 136, 0.5);
61
- padding: 0.25em 0em 0.125em 0em;
61
+ padding: 0.25em;
62
62
  display: flex;
63
- }
64
-
65
- div.app-info > span {
66
- flex: 0 0 auto;
67
- padding: 0 0.25em;
63
+ align-items: center;
68
64
  }
69
65
 
70
66
 
@@ -136,3 +136,14 @@ div.searchbox-filter-options-geometry > div.combobox {
136
136
  flex: 1 1 auto;
137
137
  height: 2em;
138
138
  }
139
+
140
+ .searchbox-entry-link {
141
+ margin-right: 0.5em;
142
+ margin-left: 0.5em;
143
+ opacity: 0.6;
144
+ transition: opacity 0.2s;
145
+ }
146
+
147
+ .searchbox-entry-link:hover {
148
+ opacity: 1;
149
+ }
@@ -27,7 +27,7 @@ import { remove as removeDiacritics } from 'diacritics';
27
27
  import isEmpty from 'lodash.isempty';
28
28
  import PropTypes from 'prop-types';
29
29
  import { addLayer, removeLayer, replacePlaceholderLayer, LayerRole } from '../../actions/layers';
30
- import { NotificationType, showNotification, closeWindow } from '../../actions/windows';
30
+ import { openExternalUrl, NotificationType, showNotification, closeWindow } from '../../actions/windows';
31
31
  import LayerUtils from '../../utils/LayerUtils';
32
32
  import LocaleUtils from '../../utils/LocaleUtils';
33
33
  import MiscUtils from '../../utils/MiscUtils';
@@ -172,6 +172,15 @@ var LayerCatalogWidget = /*#__PURE__*/function (_React$PureComponent) {
172
172
  }
173
173
  }
174
174
  });
175
+ _defineProperty(_this, "openUrl", function (ev, url, target, title) {
176
+ MiscUtils.killEvent(ev);
177
+ if (target === "iframe") {
178
+ target = ":iframedialog:externallinkiframe";
179
+ }
180
+ _this.props.openExternalUrl(url, target, {
181
+ title: title
182
+ });
183
+ });
175
184
  _this.state.catalog = props.catalog || [];
176
185
  return _this;
177
186
  }
@@ -232,6 +241,13 @@ var LayerCatalogWidget = /*#__PURE__*/function (_React$PureComponent) {
232
241
  return _this2.checkAddServiceLayer(entry, true);
233
242
  },
234
243
  title: LocaleUtils.tr("importlayer.asgroup")
244
+ }) : null, entry.link ? /*#__PURE__*/React.createElement(Icon, {
245
+ className: "layer-catalog-widget-entry-link",
246
+ icon: "info-sign",
247
+ onClick: function onClick(ev) {
248
+ return _this2.openUrl(ev, entry.link, entry.target);
249
+ },
250
+ title: LocaleUtils.tr("layercatalog.openlink")
235
251
  }) : null), entry.expanded ? sublayers : null);
236
252
  }
237
253
  }, {
@@ -287,6 +303,7 @@ _defineProperty(LayerCatalogWidget, "propTypes", {
287
303
  layers: PropTypes.array,
288
304
  levelBasedIndentSize: PropTypes.bool,
289
305
  mapCrs: PropTypes.string,
306
+ openExternalUrl: PropTypes.func,
290
307
  pendingRequests: PropTypes.number,
291
308
  removeLayer: PropTypes.func,
292
309
  replacePlaceholderLayer: PropTypes.func,
@@ -299,6 +316,7 @@ export default connect(function (state) {
299
316
  }, {
300
317
  addLayer: addLayer,
301
318
  closeWindow: closeWindow,
319
+ openExternalUrl: openExternalUrl,
302
320
  removeLayer: removeLayer,
303
321
  replacePlaceholderLayer: replacePlaceholderLayer,
304
322
  showNotification: showNotification
@@ -57,3 +57,14 @@ span.layer-catalog-widget-entry-service {
57
57
  border-radius: 0.25em;
58
58
  width: 6ch;
59
59
  }
60
+
61
+ .layer-catalog-widget-entry-link {
62
+ margin-right: 0.5em;
63
+ margin-left: 0.5em;
64
+ opacity: 0.6;
65
+ transition: opacity 0.2s;
66
+ }
67
+
68
+ .layer-catalog-widget-entry-link:hover {
69
+ opacity: 1;
70
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qwc2",
3
- "version": "2026.04.03",
3
+ "version": "2026.04.20",
4
4
  "description": "QGIS Web Client",
5
5
  "author": "Sourcepole AG",
6
6
  "license": "BSD-2-Clause",
@@ -17,7 +17,7 @@
17
17
  ],
18
18
  "dependencies": {
19
19
  "@furkot/webfonts-generator": "^2.0.3",
20
- "@giro3d/giro3d": "2.0.0-beta.1",
20
+ "@giro3d/giro3d": "^2.0.1",
21
21
  "@kayahr/text-encoding": "^2.1.0",
22
22
  "@loaders.gl/core": "^4.3.4",
23
23
  "@loaders.gl/shapefile": "^4.3.4",
@@ -32,7 +32,7 @@
32
32
  "@turf/intersect": "^7.3.3",
33
33
  "@vtaits/react-color-picker": "^2.0.0",
34
34
  "any-date-parser": "^1.5.4",
35
- "axios": "^1.13.4",
35
+ "axios": "^1.15.0",
36
36
  "buffer": "^6.0.3",
37
37
  "chart.js": "^4.5.1",
38
38
  "chartjs-adapter-dayjs-4": "^1.0.4",
package/plugins/API.js CHANGED
@@ -394,6 +394,26 @@ var API = /*#__PURE__*/function (_React$Component) {
394
394
  _defineProperty(_this, "removeIdentifyAttributeCalculator", function (name) {
395
395
  delete window.qwc2.__attributeCalculators[name];
396
396
  });
397
+ /**
398
+ * Add custom attribute formatter
399
+ * (i.e. computed attributes which are added to GetFeatureInfo responses).
400
+ *
401
+ * * `name`: An identifier
402
+ * * `fmtFunc`: The formatter function with signature `function(name, value, layer, feature)`
403
+ *
404
+ * The `fmtFunc` should return a string (which may also be a HTML fragment).
405
+ */
406
+ _defineProperty(_this, "addIdentifyAttributeFormatter", function (name, fmtFunc) {
407
+ window.qwc2.__attributeFormatters[name] = fmtFunc;
408
+ });
409
+ /**
410
+ * Remove custom identify attribute formatter
411
+ *
412
+ * * `name`: The identifier
413
+ */
414
+ _defineProperty(_this, "removeIdentifyAttributeFormatter", function (name) {
415
+ delete window.qwc2.__attributeFormatters[name];
416
+ });
397
417
  /**
398
418
  * Add custom identify exporter
399
419
  *
@@ -521,6 +541,7 @@ var API = /*#__PURE__*/function (_React$Component) {
521
541
  window.qwc2 = {
522
542
  __customPlugins: {},
523
543
  __attributeCalculators: {},
544
+ __attributeFormatters: {},
524
545
  __identifyExportes: {}
525
546
  };
526
547
  // Auto-binded functions
@@ -538,6 +559,8 @@ var API = /*#__PURE__*/function (_React$Component) {
538
559
  window.qwc2.removeIdentifyAttributeCalculator = this.removeIdentifyAttributeCalculator;
539
560
  window.qwc2.addIdentifyExporter = this.addIdentifyExporter;
540
561
  window.qwc2.removeIdentifyExporter = this.removeIdentifyExporter;
562
+ window.qwc2.addIdentifyAttributeFormatter = this.addIdentifyAttributeFormatter;
563
+ window.qwc2.removeIdentifyAttributeFormatter = this.removeIdentifyAttributeFormatter;
541
564
  window.qwc2.addExternalLayer = this.addExternalLayer;
542
565
  window.qwc2.drawScratch = this.drawScratch;
543
566
  window.qwc2.drawGeometry = this.drawGeometry;
@@ -641,6 +641,17 @@ var Editing = /*#__PURE__*/function (_React$Component) {
641
641
  selectedLayer: null
642
642
  });
643
643
  }
644
+ } else if (this.props.taskData && this.props.taskData !== prevProps.taskData) {
645
+ if (this.props.taskData.feature) {
646
+ var _this$props$taskData$ = this.props.taskData.layer.split("#"),
647
+ _this$props$taskData$2 = _slicedToArray(_this$props$taskData$, 2),
648
+ _wmsName = _this$props$taskData$2[0],
649
+ _layerName = _this$props$taskData$2[1];
650
+ var editConfig = this.props.editConfigs[_wmsName][_layerName];
651
+ this.props.iface.getFeatureById(editConfig, this.props.taskData.feature, this.props.map.projection, function (feature) {
652
+ _this2.changeSelectedLayer(_this2.props.taskData.layer, feature || _this2.props.taskData.feature);
653
+ });
654
+ } else this.changeSelectedLayer(this.props.taskData.layer);
644
655
  }
645
656
  // If click point changed and in pick mode with a selected layer, trigger a pick
646
657
  var isCurrentContext = this.props.editContext.id === this.props.currentEditContext;
@@ -652,10 +663,10 @@ var Editing = /*#__PURE__*/function (_React$Component) {
652
663
  var scale = Math.round(MapUtils.computeForZoom(this.props.map.scales, this.props.map.zoom));
653
664
  var _this$state$selectedL3 = this.state.selectedLayer.split("#"),
654
665
  _this$state$selectedL4 = _slicedToArray(_this$state$selectedL3, 2),
655
- _wmsName = _this$state$selectedL4[0],
656
- _layerName = _this$state$selectedL4[1];
657
- var editConfig = this.props.editConfigs[_wmsName][_layerName];
658
- this.props.iface.getFeature(editConfig, newPoint.coordinate, this.props.map.projection, scale, 96, function (featureCollection) {
666
+ _wmsName2 = _this$state$selectedL4[0],
667
+ _layerName2 = _this$state$selectedL4[1];
668
+ var _editConfig = this.props.editConfigs[_wmsName2][_layerName2];
669
+ this.props.iface.getFeature(_editConfig, newPoint.coordinate, this.props.map.projection, scale, 96, function (featureCollection) {
659
670
  var features = featureCollection ? featureCollection.features : null;
660
671
  _this2.setState({
661
672
  pickedFeatures: features
@@ -294,7 +294,8 @@ var MapExport = /*#__PURE__*/function (_React$Component) {
294
294
  width: 0,
295
295
  height: 0,
296
296
  scale: null,
297
- pageSize: null
297
+ pageSize: null,
298
+ exporting: false
298
299
  });
299
300
  });
300
301
  _defineProperty(_this, "geometryChanged", function (center, extents, rotation, scale) {
@@ -448,18 +449,37 @@ var MapExport = /*#__PURE__*/function (_React$Component) {
448
449
  });
449
450
  });
450
451
  _defineProperty(_this, "dxfExport", function (baseParams, fileName, formatConfiguration) {
451
- var promises = _this.props.layers.filter(function (layer) {
452
- var _layer$mapFormats;
453
- return layer.type === 'wms' && layer.role > LayerRole.BACKGROUND && ((_layer$mapFormats = layer.mapFormats) === null || _layer$mapFormats === void 0 ? void 0 : _layer$mapFormats.includes("application/dxf"));
454
- }).reverse().map(function (layer) {
455
- var _layer$params$FILTER, _layer$params$FILTER_;
456
- var params = _objectSpread(_objectSpread({}, baseParams), {}, {
457
- LAYERS: layer.params.LAYERS,
458
- OPACITIES: layer.params.OPACITIES,
459
- STYLES: layer.params.STYLES,
460
- FILTER: (_layer$params$FILTER = layer.params.FILTER) !== null && _layer$params$FILTER !== void 0 ? _layer$params$FILTER : '',
461
- FILTER_GEOM: (_layer$params$FILTER_ = layer.params.FILTER_GEOM) !== null && _layer$params$FILTER_ !== void 0 ? _layer$params$FILTER_ : ''
452
+ // If formatConfiguration overrides LAYERS, only export theme layer with those layers
453
+ var layersOverridden = (formatConfiguration.extraQuery || "").split(/[?&]/).find(function (entry) {
454
+ return entry.split("=")[0].toLowerCase() === "layers";
455
+ }) !== undefined;
456
+ var jobs = [];
457
+ if (layersOverridden) {
458
+ jobs.push({
459
+ layer: _this.props.theme,
460
+ params: baseParams
462
461
  });
462
+ } else {
463
+ jobs = _this.props.layers.filter(function (layer) {
464
+ var _layer$mapFormats;
465
+ return layer.type === 'wms' && layer.role > LayerRole.BACKGROUND && ((_layer$mapFormats = layer.mapFormats) === null || _layer$mapFormats === void 0 ? void 0 : _layer$mapFormats.includes("application/dxf"));
466
+ }).reverse().map(function (layer) {
467
+ var _layer$params$FILTER, _layer$params$FILTER_;
468
+ return {
469
+ layer: layer,
470
+ params: _objectSpread(_objectSpread({}, baseParams), {}, {
471
+ LAYERS: layer.params.LAYERS,
472
+ OPACITIES: layer.params.OPACITIES,
473
+ STYLES: layer.params.STYLES,
474
+ FILTER: (_layer$params$FILTER = layer.params.FILTER) !== null && _layer$params$FILTER !== void 0 ? _layer$params$FILTER : '',
475
+ FILTER_GEOM: (_layer$params$FILTER_ = layer.params.FILTER_GEOM) !== null && _layer$params$FILTER_ !== void 0 ? _layer$params$FILTER_ : ''
476
+ })
477
+ };
478
+ });
479
+ }
480
+ var promises = jobs.map(function (_ref3) {
481
+ var layer = _ref3.layer,
482
+ params = _ref3.params;
463
483
  var data = Object.entries(params).map(function (pair) {
464
484
  return pair.map(function (entry) {
465
485
  return encodeURIComponent(entry).replace(/%20/g, '+');
@@ -485,21 +505,26 @@ var MapExport = /*#__PURE__*/function (_React$Component) {
485
505
  Promise.all(promises).then(function (responses) {
486
506
  var decoder = new TextDecoder("iso-8859-1");
487
507
  var dxfDocuments = responses.filter(function (response) {
488
- return response.headers['content-type'] === "application/dxf";
508
+ return response && response.headers['content-type'] === "application/dxf";
489
509
  }).map(function (response) {
490
510
  return explodeDxf(decoder.decode(response.data));
491
511
  });
492
- var dxfDocument = mergeDxf(dxfDocuments);
493
- var result = implodeDxf(dxfDocument);
494
- var encoder = new TextEncoder("iso-8859-1");
495
- FileSaver.saveAs(new Blob([encoder.encode(result)], {
496
- type: "application/dxf"
497
- }), fileName);
498
- /*
499
- responses.forEach((response, idx) => {
500
- FileSaver.saveAs(new Blob([response.data], {type: "application/dxf"}), "orig_" + idx + "_" + fileName);
501
- });
502
- */
512
+ if (dxfDocuments.length !== 0) {
513
+ var dxfDocument = mergeDxf(dxfDocuments);
514
+ var result = implodeDxf(dxfDocument);
515
+ var encoder = new TextEncoder("iso-8859-1");
516
+ FileSaver.saveAs(new Blob([encoder.encode(result)], {
517
+ type: "application/dxf"
518
+ }), fileName);
519
+ /*
520
+ responses.forEach((response, idx) => {
521
+ FileSaver.saveAs(new Blob([response.data], {type: "application/dxf"}), "orig_" + idx + "_" + fileName);
522
+ });
523
+ */
524
+ } else {
525
+ /* eslint-disable-next-line */
526
+ alert('Export failed');
527
+ }
503
528
  _this.setState({
504
529
  exporting: false
505
530
  });
@@ -573,7 +598,7 @@ _defineProperty(MapExport, "propTypes", {
573
598
  fileNameTemplate: PropTypes.string,
574
599
  /** Formats to force as available even if the map capabilities report otherwise. Useful if a serviceUrl is defined in a format configuration. */
575
600
  forceAvailableFormats: PropTypes.array,
576
- /** Custom export configuration per format.
601
+ /** Custom format export configuration. Specify a format mime-type (i.e. `application/dxf`) as key, and an array of one or more configurations as value.
577
602
  * If more than one configuration per format is provided, a selection combo will be displayed.
578
603
  * `labelMsgId` is a translation string message id for the combo label. If not defined, `name` will be displayed.
579
604
  * `extraQuery` will be appended to the query string (replacing any existing parameters).
@@ -581,17 +606,15 @@ _defineProperty(MapExport, "propTypes", {
581
606
  * `baseLayer` will be appended to the LAYERS instead of the background layer.
582
607
  * `projections` is a list of export projections. If empty, the map projection is automatically used.
583
608
  * `serviceUrl` is the address of a custom service to use instead of the layer OWS service url. */
584
- formatConfiguration: PropTypes.shape({
585
- format: PropTypes.arrayOf(PropTypes.shape({
586
- name: PropTypes.string,
587
- labelMsgId: PropTypes.string,
588
- extraQuery: PropTypes.string,
589
- formatOptions: PropTypes.string,
590
- baseLayer: PropTypes.string,
591
- projections: PropTypes.array,
592
- serviceUrl: PropTypes.string
593
- }))
594
- }),
609
+ formatConfiguration: PropTypes.objectOf(PropTypes.arrayOf(PropTypes.shape({
610
+ name: PropTypes.string,
611
+ labelMsgId: PropTypes.string,
612
+ extraQuery: PropTypes.string,
613
+ formatOptions: PropTypes.string,
614
+ baseLayer: PropTypes.string,
615
+ projections: PropTypes.array,
616
+ serviceUrl: PropTypes.string
617
+ }))),
595
618
  layers: PropTypes.array,
596
619
  map: PropTypes.object,
597
620
  /** List of image sizes to offer, in addition to the free-hand selection. The width and height are in millimeters. */
@@ -53,7 +53,7 @@ import './style/MapInfoTooltip.css';
53
53
  * [ElevationInterface.js](https://github.com/qgis/qwc2/blob/master/utils/ElevationInterface.js)), the
54
54
  * height at the picked position is also displayed.
55
55
  *
56
- * If `mapInfoService` in `config.json` points to a `qwc-mapinfo-service`, additional
56
+ * If `mapInfoServiceUrl` in `config.json` points to a `qwc-mapinfo-service`, additional
57
57
  * custom information according to the `qwc-mapinfo-service` configuration is returned.
58
58
  *
59
59
  * You can pass additional plugin components to the `MapInfoTooltip` in `appConfig.js`:
@@ -122,7 +122,7 @@ var MapInfoTooltip = /*#__PURE__*/function (_React$Component) {
122
122
  elevation: elevation
123
123
  });
124
124
  })["catch"](function () {});
125
- var mapInfoService = ConfigUtils.getConfigProp("mapInfoService");
125
+ var mapInfoService = ConfigUtils.getConfigProp("mapInfoServiceUrl") || ConfigUtils.getConfigProp("mapInfoService");
126
126
  if (mapInfoService) {
127
127
  axios.get(mapInfoService, {
128
128
  params: {
@@ -284,6 +284,7 @@ _defineProperty(MapInfoTooltip, "propTypes", {
284
284
  /** The number of decimal places to display for elevation values. */
285
285
  elevationPrecision: PropTypes.number,
286
286
  enabled: PropTypes.bool,
287
+ /** Whether to display WGS84 coordinates in addition to map CRS coordinates. */
287
288
  includeWGS84: PropTypes.bool,
288
289
  map: PropTypes.object,
289
290
  /** Additional plugin components for the map info tooltip. */
@@ -125,7 +125,6 @@ var NewsPopup = /*#__PURE__*/function (_React$Component) {
125
125
  }
126
126
  }]);
127
127
  }(React.Component);
128
- _defineProperty(NewsPopup, "availableIn3D", true);
129
128
  _defineProperty(NewsPopup, "propTypes", {
130
129
  /** URL to the news HTML document to display in the popup. Can contain `{lang}` as a placeholder which will be replaced with the current viewer language.*/
131
130
  newsDocument: PropTypes.string,
package/plugins/Print.js CHANGED
@@ -696,6 +696,22 @@ var Print = /*#__PURE__*/function (_React$Component) {
696
696
  formData[mapName + ":STYLES"] = printParams.STYLES;
697
697
  formData[mapName + ":FILTER"] = printParams.FILTER;
698
698
  formData[mapName + ":FILTER_GEOM"] = printParams.FILTER_GEOM;
699
+ if (_this.state.layout.map.followPresetName in _this.props.theme.visibilityPresets) {
700
+ var preset = _this.props.theme.visibilityPresets[_this.state.layout.map.followPresetName];
701
+ var layers = [];
702
+ var styles = [];
703
+ Object.entries(preset).forEach(function (_ref5) {
704
+ var _ref6 = _slicedToArray(_ref5, 2),
705
+ layerPath = _ref6[0],
706
+ style = _ref6[1];
707
+ if (style) {
708
+ layers.push(layerPath.split("/").slice(-1)[0]);
709
+ styles.push(style);
710
+ }
711
+ });
712
+ formData[mapName + ":LAYERS"] = layers.join(",");
713
+ formData[mapName + ":STYLES"] = styles.join(",");
714
+ }
699
715
 
700
716
  // Add highlight params
701
717
  var printDpi = parseInt(_this.state.dpi, 10) || 0;
@@ -732,10 +748,10 @@ var Print = /*#__PURE__*/function (_React$Component) {
732
748
  // Add dimension values
733
749
  _this.props.layers.forEach(function (layer) {
734
750
  if (layer.role === LayerRole.THEME) {
735
- Object.entries(layer.dimensionValues || {}).forEach(function (_ref5) {
736
- var _ref6 = _slicedToArray(_ref5, 2),
737
- key = _ref6[0],
738
- value = _ref6[1];
751
+ Object.entries(layer.dimensionValues || {}).forEach(function (_ref7) {
752
+ var _ref8 = _slicedToArray(_ref7, 2),
753
+ key = _ref8[0],
754
+ value = _ref8[1];
739
755
  if (value !== undefined) {
740
756
  formData[key] = value;
741
757
  }
@@ -747,10 +763,10 @@ var Print = /*#__PURE__*/function (_React$Component) {
747
763
  var extraOptions = Object.fromEntries((_this.props.theme.extraPrintParameters || "").split("&").filter(Boolean).map(function (entry) {
748
764
  return entry.split("=");
749
765
  }));
750
- Object.entries(extraOptions).forEach(function (_ref7) {
751
- var _ref8 = _slicedToArray(_ref7, 2),
752
- key = _ref8[0],
753
- value = _ref8[1];
766
+ Object.entries(extraOptions).forEach(function (_ref9) {
767
+ var _ref0 = _slicedToArray(_ref9, 2),
768
+ key = _ref0[0],
769
+ value = _ref0[1];
754
770
  formData[key] = value;
755
771
  });
756
772
  var pages = [formData];
@@ -784,8 +800,8 @@ var Print = /*#__PURE__*/function (_React$Component) {
784
800
  });
785
801
  });
786
802
  _defineProperty(_this, "translateLayoutName", function (item) {
787
- var _ref9, _ref0, _this$props$theme$tra, _this$props$theme$tra2, _this$props$theme$tra3;
788
- return (_ref9 = (_ref0 = (_this$props$theme$tra = (_this$props$theme$tra2 = _this.props.theme.translations) === null || _this$props$theme$tra2 === void 0 || (_this$props$theme$tra2 = _this$props$theme$tra2.layouts) === null || _this$props$theme$tra2 === void 0 ? void 0 : _this$props$theme$tra2[item.title]) !== null && _this$props$theme$tra !== void 0 ? _this$props$theme$tra : (_this$props$theme$tra3 = _this.props.theme.translations) === null || _this$props$theme$tra3 === void 0 || (_this$props$theme$tra3 = _this$props$theme$tra3.layouts) === null || _this$props$theme$tra3 === void 0 ? void 0 : _this$props$theme$tra3[item.name]) !== null && _ref0 !== void 0 ? _ref0 : item.title) !== null && _ref9 !== void 0 ? _ref9 : item.name;
803
+ var _ref1, _ref10, _this$props$theme$tra, _this$props$theme$tra2, _this$props$theme$tra3;
804
+ return (_ref1 = (_ref10 = (_this$props$theme$tra = (_this$props$theme$tra2 = _this.props.theme.translations) === null || _this$props$theme$tra2 === void 0 || (_this$props$theme$tra2 = _this$props$theme$tra2.layouts) === null || _this$props$theme$tra2 === void 0 ? void 0 : _this$props$theme$tra2[item.title]) !== null && _this$props$theme$tra !== void 0 ? _this$props$theme$tra : (_this$props$theme$tra3 = _this.props.theme.translations) === null || _this$props$theme$tra3 === void 0 || (_this$props$theme$tra3 = _this$props$theme$tra3.layouts) === null || _this$props$theme$tra3 === void 0 ? void 0 : _this$props$theme$tra3[item.name]) !== null && _ref10 !== void 0 ? _ref10 : item.title) !== null && _ref1 !== void 0 ? _ref1 : item.name;
789
805
  });
790
806
  _this.printForm = null;
791
807
  _this.state.grid = props.gridInitiallyEnabled;
@@ -897,13 +897,13 @@ var SensorThingsTool = /*#__PURE__*/function (_React$Component) {
897
897
  }), /*#__PURE__*/React.createElement("button", {
898
898
  className: "button",
899
899
  onClick: _this.zoomIn,
900
- title: LocaleUtils.tr("sensorthingstool.zoomIn")
900
+ title: LocaleUtils.tr("common.zoomin")
901
901
  }, /*#__PURE__*/React.createElement(Icon, {
902
902
  icon: "zoomin"
903
903
  })), /*#__PURE__*/React.createElement("button", {
904
904
  className: "button",
905
905
  onClick: _this.zoomOut,
906
- title: LocaleUtils.tr("sensorthingstool.zoomOut")
906
+ title: LocaleUtils.tr("common.zoomout")
907
907
  }, /*#__PURE__*/React.createElement(Icon, {
908
908
  icon: "zoomout"
909
909
  })), /*#__PURE__*/React.createElement("button", {