qwc2 2025.10.30 → 2025.11.12

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 (55) hide show
  1. package/actions/layers.js +6 -29
  2. package/components/AttributeForm.js +106 -77
  3. package/components/AttributeTableWidget.js +89 -88
  4. package/components/IdentifyViewer.js +4 -2
  5. package/components/LinkFeatureForm.js +17 -9
  6. package/components/LocationRecorder.js +1 -1
  7. package/components/PickFeature.js +45 -33
  8. package/components/QtDesignerForm.js +20 -18
  9. package/components/StandardApp.js +4 -0
  10. package/components/ThemeList.js +9 -10
  11. package/components/map3d/Map3D.js +6 -3
  12. package/package.json +2 -1
  13. package/plugins/Cyclomedia.js +5 -4
  14. package/plugins/Editing.js +307 -72
  15. package/plugins/FeatureForm.js +103 -111
  16. package/plugins/LayerTree.js +4 -1
  17. package/plugins/NewsPopup.js +2 -2
  18. package/plugins/Portal.js +3 -1
  19. package/plugins/Print.js +4 -4
  20. package/plugins/Redlining.js +9 -0
  21. package/plugins/ThemeSwitcher.js +3 -0
  22. package/plugins/map/EditingSupport.js +2 -2
  23. package/plugins/map/RedliningSupport.js +63 -13
  24. package/plugins/style/Editing.css +34 -0
  25. package/reducers/editing.js +12 -7
  26. package/reducers/layers.js +27 -5
  27. package/static/translations/bg-BG.json +10 -0
  28. package/static/translations/ca-ES.json +10 -0
  29. package/static/translations/cs-CZ.json +10 -0
  30. package/static/translations/de-CH.json +10 -0
  31. package/static/translations/de-DE.json +10 -0
  32. package/static/translations/en-US.json +11 -1
  33. package/static/translations/es-ES.json +10 -0
  34. package/static/translations/fi-FI.json +10 -0
  35. package/static/translations/fr-FR.json +10 -0
  36. package/static/translations/hu-HU.json +10 -0
  37. package/static/translations/it-IT.json +10 -0
  38. package/static/translations/ja-JP.json +10 -0
  39. package/static/translations/nl-NL.json +10 -0
  40. package/static/translations/no-NO.json +10 -0
  41. package/static/translations/pl-PL.json +10 -0
  42. package/static/translations/pt-BR.json +10 -0
  43. package/static/translations/pt-PT.json +10 -0
  44. package/static/translations/ro-RO.json +10 -0
  45. package/static/translations/ru-RU.json +10 -0
  46. package/static/translations/sv-SE.json +10 -0
  47. package/static/translations/tr-TR.json +10 -0
  48. package/static/translations/tsconfig.json +10 -0
  49. package/static/translations/uk-UA.json +10 -0
  50. package/utils/EditingUtils.js +15 -13
  51. package/utils/ElevationInterface.js +1 -2
  52. package/utils/LayerUtils.js +5 -3
  53. package/utils/MapUtils.js +2 -11
  54. package/utils/ServiceLayerUtils.js +53 -2
  55. package/utils/ThemeUtils.js +3 -1
@@ -33,7 +33,6 @@ import { connect } from 'react-redux';
33
33
  import isEmpty from 'lodash.isempty';
34
34
  import PropTypes from 'prop-types';
35
35
  import { setEditContext, clearEditContext } from '../actions/editing';
36
- import { LayerRole } from '../actions/layers';
37
36
  import { setCurrentTask } from '../actions/task';
38
37
  import AttributeForm from '../components/AttributeForm';
39
38
  import ResizeableWindow from '../components/ResizeableWindow';
@@ -81,56 +80,47 @@ var FeatureForm = /*#__PURE__*/function (_React$Component) {
81
80
  });
82
81
  _defineProperty(_this, "queryFeatures", function (pos) {
83
82
  var pendingRequests = 0;
84
- Object.entries(_this.props.theme.editConfig || {}).forEach(function (_ref) {
85
- var _this$props$filter$fi;
83
+ Object.entries(_this.props.editConfigs).forEach(function (_ref) {
86
84
  var _ref2 = _slicedToArray(_ref, 2),
87
- layerId = _ref2[0],
88
- editConfig = _ref2[1];
89
- if (!editConfig.geomType) {
90
- // Skip geometryless datasets
91
- return;
92
- }
93
- var path = [];
94
- var sublayer = null;
95
- var mapScale = MapUtils.computeForZoom(_this.props.map.scales, _this.props.map.zoom);
96
- var layer = _this.props.layers.find(function (l) {
97
- return l.role === LayerRole.THEME && (sublayer = LayerUtils.searchSubLayer(l, 'name', layerId, path));
98
- });
99
- if (!layer || !sublayer || !LayerUtils.sublayerVisible(layer, path) || !LayerUtils.layerScaleInRange(sublayer, mapScale)) {
100
- return;
101
- }
102
- var layerOrder = layer.params.LAYERS.split(",");
103
- ++pendingRequests;
104
- var scale = Math.round(MapUtils.computeForZoom(_this.props.map.scales, _this.props.map.zoom));
105
- _this.props.iface.getFeature(editConfig, pos, _this.props.map.projection, scale, 96, function (featureCollection) {
106
- if (featureCollection && !isEmpty(featureCollection.features)) {
107
- _this.setState(function (state) {
108
- var newPickedFeatures = Object.fromEntries(Object.entries(_objectSpread(_objectSpread({}, state.pickedFeatures), featureCollection.features.reduce(function (res, feature) {
109
- return _objectSpread(_objectSpread({}, res), {}, _defineProperty({}, layerId + "::" + feature.id, feature));
110
- }, {}))).sort(function (a, b) {
111
- var partsA = a[0].split("::");
112
- var partsB = b[0].split("::");
113
- var diff = layerOrder.indexOf(partsB[0]) - layerOrder.indexOf(partsA[0]);
114
- return diff === 0 ? partsA[1].localeCompare(partsB[1]) : diff;
115
- }));
116
- var selectedFeature = state.pendingRequests <= 1 && !state.selectedFeature ? Object.keys(newPickedFeatures)[0] : "";
117
- return {
118
- pickedFeatures: newPickedFeatures,
119
- pendingRequests: state.pendingRequests - 1,
120
- selectedFeature: selectedFeature
121
- };
122
- });
123
- } else {
85
+ mapName = _ref2[0],
86
+ editConfigs = _ref2[1];
87
+ Object.entries(editConfigs).forEach(function (_ref3) {
88
+ var _this$props$filter$fi;
89
+ var _ref4 = _slicedToArray(_ref3, 2),
90
+ layerName = _ref4[0],
91
+ editConfig = _ref4[1];
92
+ if (!editConfig.geomType) {
93
+ // Skip geometryless datasets
94
+ return;
95
+ }
96
+ var match = LayerUtils.searchLayer(_this.props.layers, 'wms_name', mapName, 'name', layerName);
97
+ var mapScale = MapUtils.computeForZoom(_this.props.map.scales, _this.props.map.zoom);
98
+ if (!match || !LayerUtils.sublayerVisible(match.layer, match.path) || !LayerUtils.layerScaleInRange(match.sublayer, mapScale)) {
99
+ return;
100
+ }
101
+ var layerOrder = match.layer.params.LAYERS.split(",");
102
+ ++pendingRequests;
103
+ var scale = Math.round(MapUtils.computeForZoom(_this.props.map.scales, _this.props.map.zoom));
104
+ _this.props.iface.getFeature(editConfig, pos, _this.props.map.projection, scale, 96, function (featureCollection) {
124
105
  _this.setState(function (state) {
125
- var _Object$keys$;
126
- var selectedFeature = state.pendingRequests <= 1 && !state.selectedFeature ? (_Object$keys$ = Object.keys(state.pickedFeatures)[0]) !== null && _Object$keys$ !== void 0 ? _Object$keys$ : "" : "";
106
+ var pickedFeatures = state.pickedFeatures;
107
+ if (!isEmpty(featureCollection === null || featureCollection === void 0 ? void 0 : featureCollection.features)) {
108
+ pickedFeatures = Object.fromEntries(Object.entries(_objectSpread(_objectSpread({}, state.pickedFeatures), featureCollection.features.reduce(function (res, feature) {
109
+ return _objectSpread(_objectSpread({}, res), {}, _defineProperty({}, mapName + "#" + layerName + "#" + feature.id, feature));
110
+ }, {}))).sort(function (a, b) {
111
+ var partsA = a[0].split("#");
112
+ var partsB = b[0].split("#");
113
+ var diff = layerOrder.indexOf(partsB[1]) - layerOrder.indexOf(partsA[1]);
114
+ return diff === 0 ? partsA[1].localeCompare(partsB[1]) : diff;
115
+ }));
116
+ }
127
117
  return {
128
- pendingRequests: state.pendingRequests - 1,
129
- selectedFeature: selectedFeature
118
+ pickedFeatures: pickedFeatures,
119
+ pendingRequests: state.pendingRequests - 1
130
120
  };
131
121
  });
132
- }
133
- }, (_this$props$filter$fi = _this.props.filter.filterParams) === null || _this$props$filter$fi === void 0 ? void 0 : _this$props$filter$fi[sublayer.name], _this.props.filter.filterGeom);
122
+ }, (_this$props$filter$fi = _this.props.filter.filterParams) === null || _this$props$filter$fi === void 0 ? void 0 : _this$props$filter$fi[match.sublayer.name], _this.props.filter.filterGeom);
123
+ });
134
124
  });
135
125
  _this.setState({
136
126
  pendingRequests: pendingRequests,
@@ -138,29 +128,46 @@ var FeatureForm = /*#__PURE__*/function (_React$Component) {
138
128
  selectedFeature: ""
139
129
  });
140
130
  });
141
- _defineProperty(_this, "setSelectedFeature", function (ev) {
131
+ _defineProperty(_this, "setSelectedFeature", function (selectedFeature) {
132
+ if (selectedFeature) {
133
+ var _selectedFeature$spli = selectedFeature.split("#", 2),
134
+ _selectedFeature$spli2 = _slicedToArray(_selectedFeature$spli, 2),
135
+ mapName = _selectedFeature$spli2[0],
136
+ layerName = _selectedFeature$spli2[1];
137
+ var editConfig = _this.props.editConfigs[mapName][layerName];
138
+ _this.props.setEditContext('FeatureForm', {
139
+ action: 'Pick',
140
+ feature: _this.state.pickedFeatures[selectedFeature],
141
+ changed: false,
142
+ mapPrefix: mapName,
143
+ editConfig: editConfig
144
+ });
145
+ } else {
146
+ _this.props.clearEditContext('FeatureForm');
147
+ }
142
148
  _this.setState({
143
- selectedFeature: ev.target.value
149
+ selectedFeature: selectedFeature
144
150
  });
145
151
  });
146
152
  _defineProperty(_this, "onWindowClose", function () {
147
- _this.clearResults();
148
- if (_this.props.exitTaskOnResultsClose) {
149
- _this.props.setCurrentTask(null);
153
+ if (!_this.props.editContext.changed) {
154
+ _this.clearResults();
155
+ if (_this.props.exitTaskOnResultsClose) {
156
+ _this.props.setCurrentTask(null);
157
+ }
150
158
  }
151
159
  });
152
160
  _defineProperty(_this, "clearResults", function () {
153
- if (!_this.props.editContext.changed) {
154
- _this.setState(FeatureForm.defaultState);
155
- }
161
+ _this.props.clearEditContext('FeatureForm');
162
+ _this.setState(FeatureForm.defaultState);
156
163
  });
157
164
  _defineProperty(_this, "updatePickedFeatures", function (newfeature) {
158
165
  _this.setState(function (state) {
159
166
  return {
160
- pickedFeatures: Object.entries(state.pickedFeatures).reduce(function (res, _ref3) {
161
- var _ref4 = _slicedToArray(_ref3, 2),
162
- key = _ref4[0],
163
- feature = _ref4[1];
167
+ pickedFeatures: Object.entries(state.pickedFeatures).reduce(function (res, _ref5) {
168
+ var _ref6 = _slicedToArray(_ref5, 2),
169
+ key = _ref6[0],
170
+ feature = _ref6[1];
164
171
  res[key] = feature.id === newfeature.id ? newfeature : feature;
165
172
  return res;
166
173
  }, {})
@@ -193,43 +200,19 @@ var FeatureForm = /*#__PURE__*/function (_React$Component) {
193
200
  var mapCrs = this.props.theme.mapCrs;
194
201
  this.queryFeatures(CoordinatesUtils.reproject(c, startupParams.crs || mapCrs, mapCrs));
195
202
  }
196
- } else if (this.props.theme !== prevProps.theme) {
203
+ } else if (this.props.theme !== prevProps.theme || !this.props.enabled && prevProps.enabled) {
197
204
  this.clearResults();
198
- } else if (!this.props.enabled && prevProps.enabled) {
199
- if (this.props.clearResultsOnClose) {
200
- this.clearResults();
201
- }
202
205
  }
203
- if (this.props.enabled && !prevProps.enabled) {
204
- this.props.setEditContext('FeatureForm', {
205
- action: 'Pick'
206
- });
207
- }
208
- var isCurrentContext = this.props.editContext.id === this.props.currentEditContext;
209
- if (this.props.enabled && isCurrentContext && !this.props.editContext.changed && this.state.pendingRequests === 0) {
206
+ var isAllowedContext = [null, "FeatureForm"].includes(this.props.currentEditContext);
207
+ if (this.props.enabled && this.state.pendingRequests === 0 && isAllowedContext && !this.props.editContext.changed) {
210
208
  var clickPoint = this.queryPoint(prevProps);
211
209
  if (clickPoint) {
212
210
  this.queryFeatures(clickPoint);
213
211
  }
214
212
  }
215
- if (this.props.enabled && this.state.selectedFeature !== prevState.selectedFeature) {
216
- var feature = this.state.pickedFeatures ? this.state.pickedFeatures[this.state.selectedFeature] : null;
217
- var curLayerId = this.state.selectedFeature.split("::")[0];
218
- var curConfig = this.props.theme.editConfig[curLayerId] || {};
219
- var canEditGeometry = ['Point', 'LineString', 'Polygon'].includes((curConfig.geomType || "").replace(/^Multi/, '').replace(/Z$/, ''));
220
- var editPermissions = curConfig.permissions || {};
221
- this.props.setEditContext('FeatureForm', {
222
- action: 'Pick',
223
- feature: feature,
224
- changed: false,
225
- geomType: curConfig.geomType || null,
226
- geomReadOnly: editPermissions.updatable === false || !canEditGeometry,
227
- permissions: (curConfig === null || curConfig === void 0 ? void 0 : curConfig.permissions) || {}
228
- });
229
- }
230
- if (!this.props.enabled && prevProps.enabled) {
231
- this.props.clearEditContext('FeatureForm');
232
- this.setState(FeatureForm.defaultState);
213
+ if (this.state.pendingRequests === 0 && prevState.pendingRequests > 0) {
214
+ // Select first result
215
+ this.setSelectedFeature(Object.keys(this.state.pickedFeatures)[0]);
233
216
  }
234
217
  }
235
218
  }, {
@@ -255,39 +238,48 @@ var FeatureForm = /*#__PURE__*/function (_React$Component) {
255
238
  }, LocaleUtils.tr("featureform.noresults")));
256
239
  } else {
257
240
  var featureText = LocaleUtils.tr("featureform.feature");
258
- var curLayerId = this.state.selectedFeature.split("::")[0];
259
- var curConfig = this.props.theme.editConfig[curLayerId];
241
+ var attributeForm = null;
242
+ if (this.props.editContext.feature) {
243
+ var _this$props$layers$fi;
244
+ var translations = (_this$props$layers$fi = this.props.layers.find(function (layer) {
245
+ return layer.wms_name === _this2.props.editContext.mapPrefix;
246
+ })) === null || _this$props$layers$fi === void 0 ? void 0 : _this$props$layers$fi.translations;
247
+ attributeForm = /*#__PURE__*/React.createElement(AttributeForm, {
248
+ editContext: this.props.editContext,
249
+ iface: this.props.iface,
250
+ onCommit: this.updatePickedFeatures,
251
+ translations: translations
252
+ });
253
+ }
260
254
  body = /*#__PURE__*/React.createElement("div", {
261
255
  className: "feature-query-body",
262
256
  role: "body"
263
257
  }, Object.keys(this.state.pickedFeatures).length > 1 ? /*#__PURE__*/React.createElement("div", {
264
258
  className: "feature-query-selection"
265
259
  }, /*#__PURE__*/React.createElement("select", {
266
- onChange: this.setSelectedFeature,
260
+ onChange: function onChange(ev) {
261
+ return _this2.setSelectedFeature(ev.target.value);
262
+ },
267
263
  value: this.state.selectedFeature
268
- }, Object.entries(this.state.pickedFeatures).map(function (_ref5) {
269
- var _this2$props$theme$tr, _this2$props$theme$tr2, _LayerUtils$searchLay, _LayerUtils$searchLay2;
270
- var _ref6 = _slicedToArray(_ref5, 2),
271
- id = _ref6[0],
272
- feature = _ref6[1];
273
- var _id$split = id.split("::"),
274
- _id$split2 = _slicedToArray(_id$split, 2),
275
- layerId = _id$split2[0],
276
- featureId = _id$split2[1];
277
- var editConfig = _this2.props.theme.editConfig[layerId];
278
- var layerName = editConfig.layerName;
279
- var layerTitle = editConfig.layerTitle ? (_this2$props$theme$tr = (_this2$props$theme$tr2 = _this2.props.theme.translations) === null || _this2$props$theme$tr2 === void 0 || (_this2$props$theme$tr2 = _this2$props$theme$tr2.layertree) === null || _this2$props$theme$tr2 === void 0 ? void 0 : _this2$props$theme$tr2[layerName]) !== null && _this2$props$theme$tr !== void 0 ? _this2$props$theme$tr : editConfig.layerTitle : (_LayerUtils$searchLay = (_LayerUtils$searchLay2 = LayerUtils.searchLayer(_this2.props.layers, _this2.props.theme.url, layerName)) === null || _LayerUtils$searchLay2 === void 0 || (_LayerUtils$searchLay2 = _LayerUtils$searchLay2.sublayer) === null || _LayerUtils$searchLay2 === void 0 ? void 0 : _LayerUtils$searchLay2.title) !== null && _LayerUtils$searchLay !== void 0 ? _LayerUtils$searchLay : layerName;
264
+ }, Object.entries(this.state.pickedFeatures).map(function (_ref7) {
265
+ var _ref9, _ref10, _match$layer$translat, _match$layer$translat2, _match$sublayer;
266
+ var _ref8 = _slicedToArray(_ref7, 2),
267
+ id = _ref8[0],
268
+ feature = _ref8[1];
269
+ var _id$split = id.split("#"),
270
+ _id$split2 = _slicedToArray(_id$split, 3),
271
+ mapName = _id$split2[0],
272
+ layerName = _id$split2[1],
273
+ featureId = _id$split2[2];
274
+ var editConfig = _this2.props.editConfigs[mapName][layerName];
275
+ var match = LayerUtils.searchLayer(_this2.props.layers, 'wms_name', mapName, 'name', layerName);
276
+ var layerTitle = (_ref9 = (_ref10 = (_match$layer$translat = (_match$layer$translat2 = match.layer.translations) === null || _match$layer$translat2 === void 0 || (_match$layer$translat2 = _match$layer$translat2.layertree) === null || _match$layer$translat2 === void 0 ? void 0 : _match$layer$translat2[layerName]) !== null && _match$layer$translat !== void 0 ? _match$layer$translat : editConfig.layerTitle) !== null && _ref10 !== void 0 ? _ref10 : match === null || match === void 0 || (_match$sublayer = match.sublayer) === null || _match$sublayer === void 0 ? void 0 : _match$sublayer.title) !== null && _ref9 !== void 0 ? _ref9 : layerName;
280
277
  var featureName = editConfig.displayField ? feature.properties[editConfig.displayField] : featureText + " " + featureId;
281
278
  return /*#__PURE__*/React.createElement("option", {
282
279
  key: id,
283
280
  value: id
284
281
  }, layerTitle + ": " + featureName);
285
- }))) : null, this.props.editContext.feature ? /*#__PURE__*/React.createElement(AttributeForm, {
286
- editConfig: curConfig,
287
- editContext: this.props.editContext,
288
- iface: this.props.iface,
289
- onCommit: this.updatePickedFeatures
290
- }) : null);
282
+ }))) : null, attributeForm);
291
283
  }
292
284
  resultWindow = /*#__PURE__*/React.createElement(ResizeableWindow, {
293
285
  dockable: this.props.geometry.side,
@@ -315,10 +307,9 @@ var FeatureForm = /*#__PURE__*/function (_React$Component) {
315
307
  }(React.Component);
316
308
  _defineProperty(FeatureForm, "propTypes", {
317
309
  clearEditContext: PropTypes.func,
318
- /** Whether to clear the identify results when exiting the identify tool. */
319
- clearResultsOnClose: PropTypes.bool,
320
310
  click: PropTypes.object,
321
311
  currentEditContext: PropTypes.string,
312
+ editConfigs: PropTypes.object,
322
313
  editContext: PropTypes.object,
323
314
  enabled: PropTypes.bool,
324
315
  /** Whether to clear the task when the results window is closed. */
@@ -372,6 +363,7 @@ export default (function () {
372
363
  layers: state.layers.flat,
373
364
  filter: state.layers.filter,
374
365
  map: state.map,
366
+ editConfigs: state.layers.editConfigs,
375
367
  theme: state.theme.current,
376
368
  startupParams: state.localConfig.startupParams
377
369
  };
@@ -401,6 +401,7 @@ var LayerTree = /*#__PURE__*/function (_React$Component) {
401
401
  }) : null);
402
402
  });
403
403
  _defineProperty(_this, "renderOptionsMenu", function (layer, sublayer, path, marginRight) {
404
+ var _this$props$editConfi;
404
405
  var subtreevisibility = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 0;
405
406
  var allowReordering = ConfigUtils.getConfigProp("allowReorderingLayers", _this.props.theme) === true;
406
407
  var zoomToLayerButton = null;
@@ -453,7 +454,7 @@ var LayerTree = /*#__PURE__*/function (_React$Component) {
453
454
  });
454
455
  }
455
456
  var attrTableButton = null;
456
- if (_this.props.showAttributeTableLink && ConfigUtils.havePlugin("AttributeTable") && layer.role === LayerRole.THEME && _this.props.theme.editConfig[sublayer.name]) {
457
+ if (_this.props.showAttributeTableLink && ConfigUtils.havePlugin("AttributeTable") && layer.role === LayerRole.THEME && (_this$props$editConfi = _this.props.editConfigs[layer.wms_name]) !== null && _this$props$editConfi !== void 0 && _this$props$editConfi[sublayer.name]) {
457
458
  attrTableButton = /*#__PURE__*/React.createElement(Icon, {
458
459
  icon: "editing",
459
460
  onClick: function onClick() {
@@ -1079,6 +1080,7 @@ _defineProperty(LayerTree, "propTypes", {
1079
1080
  /** Whether to display a BBOX dependent legend. Can be `true|false|"theme"`, latter means only for theme layers. */
1080
1081
  bboxDependentLegend: PropTypes.oneOfType([PropTypes.bool, PropTypes.string]),
1081
1082
  changeLayerProperty: PropTypes.func,
1083
+ editConfigs: PropTypes.object,
1082
1084
  /** Whether to enable the legend print functionality. */
1083
1085
  enableLegendPrint: PropTypes.bool,
1084
1086
  /** Whether to display a service info button to display the WMS service metadata. */
@@ -1178,6 +1180,7 @@ _defineProperty(LayerTree, "defaultProps", {
1178
1180
  });
1179
1181
  var selector = function selector(state) {
1180
1182
  return {
1183
+ editConfigs: state.layers.editConfigs,
1181
1184
  layers: state.layers.flat,
1182
1185
  filter: state.layers.filter,
1183
1186
  loadingLayers: state.layers.loading,
@@ -52,7 +52,7 @@ var NewsPopup = /*#__PURE__*/function (_React$Component) {
52
52
  className: "newspopup-dialog-popup-body",
53
53
  role: "body"
54
54
  }, /*#__PURE__*/React.createElement("iframe", {
55
- src: _this.props.newsDocument
55
+ src: _this.props.newsDocument.replace('{lang}', LocaleUtils.lang())
56
56
  }), /*#__PURE__*/React.createElement("div", {
57
57
  className: "newspopup-dialog-popup-buttonbar"
58
58
  }, /*#__PURE__*/React.createElement("button", {
@@ -127,7 +127,7 @@ var NewsPopup = /*#__PURE__*/function (_React$Component) {
127
127
  }(React.Component);
128
128
  _defineProperty(NewsPopup, "availableIn3D", true);
129
129
  _defineProperty(NewsPopup, "propTypes", {
130
- /** URL to the news HTML document to display in the popup. */
130
+ /** 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
131
  newsDocument: PropTypes.string,
132
132
  /** Revision of the document. */
133
133
  newsRev: PropTypes.string,
package/plugins/Portal.js CHANGED
@@ -133,6 +133,7 @@ var Portal = /*#__PURE__*/function (_React$Component) {
133
133
  }, /*#__PURE__*/React.createElement(ThemeList, {
134
134
  collapsibleGroups: this.props.collapsibleGroups,
135
135
  dontPreserveSettingsOnSwitch: !preserveSettings,
136
+ expandGroups: this.props.expandGroups,
136
137
  filter: this.state.filter
137
138
  })), /*#__PURE__*/React.createElement("div", {
138
139
  className: "portal-bottombar"
@@ -169,6 +170,8 @@ _defineProperty(Portal, "propTypes", {
169
170
  collapsibleGroups: PropTypes.bool,
170
171
  currentTask: PropTypes.string,
171
172
  currentTheme: PropTypes.object,
173
+ /** Whether to expand theme groups by default. */
174
+ expandGroups: PropTypes.bool,
172
175
  keepMenuOpen: PropTypes.bool,
173
176
  /** Name of a logo image below assets/img. */
174
177
  logo: PropTypes.string,
@@ -186,7 +189,6 @@ _defineProperty(Portal, "propTypes", {
186
189
  userName: PropTypes.string
187
190
  });
188
191
  _defineProperty(Portal, "defaultProps", {
189
- collapsibleGroups: true,
190
192
  menuItems: []
191
193
  });
192
194
  var selector = function selector(state) {
package/plugins/Print.js CHANGED
@@ -254,7 +254,7 @@ var Print = /*#__PURE__*/function (_React$Component) {
254
254
  return /*#__PURE__*/React.createElement("option", {
255
255
  key: item.name,
256
256
  value: item.name
257
- }, _this.translateLayoutName(item.name));
257
+ }, _this.translateLayoutName(item));
258
258
  })))), _this.props.formats.length > 1 ? /*#__PURE__*/React.createElement("tr", null, /*#__PURE__*/React.createElement("td", null, LocaleUtils.tr("print.format")), /*#__PURE__*/React.createElement("td", null, /*#__PURE__*/React.createElement("select", {
259
259
  disabled: _this.state.printSeriesEnabled,
260
260
  name: "FORMAT",
@@ -782,9 +782,9 @@ var Print = /*#__PURE__*/function (_React$Component) {
782
782
  });
783
783
  });
784
784
  });
785
- _defineProperty(_this, "translateLayoutName", function (name) {
786
- var _this$props$theme$tra, _this$props$theme$tra2;
787
- return (_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[name]) !== null && _this$props$theme$tra !== void 0 ? _this$props$theme$tra : name;
785
+ _defineProperty(_this, "translateLayoutName", function (item) {
786
+ var _ref9, _ref10, _this$props$theme$tra, _this$props$theme$tra2, _this$props$theme$tra3;
787
+ return (_ref9 = (_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 && _ref9 !== void 0 ? _ref9 : item.name;
788
788
  });
789
789
  _this.printForm = null;
790
790
  _this.state.grid = props.gridInitiallyEnabled;
@@ -220,6 +220,15 @@ var Redlining = /*#__PURE__*/function (_React$Component) {
220
220
  geomType: null,
221
221
  text: ""
222
222
  }
223
+ } : null, toolEnabled("Clone") ? {
224
+ key: "Clone",
225
+ tooltip: LocaleUtils.tr("redlining.clone"),
226
+ icon: "clone",
227
+ data: {
228
+ action: "Clone",
229
+ geomType: null
230
+ },
231
+ disabled: !_this.props.redlining.selectedFeature
223
232
  } : null, {
224
233
  key: "Delete",
225
234
  tooltip: LocaleUtils.tr("redlining.delete"),
@@ -106,6 +106,7 @@ var ThemeSwitcher = /*#__PURE__*/function (_React$Component) {
106
106
  allowAddingOtherThemeLayers: showAddThemeLayersButton,
107
107
  allowAddingOtherThemes: showAddThemeButton,
108
108
  collapsibleGroups: _this2.props.collapsibleGroups,
109
+ expandGroups: _this2.props.expandGroups,
109
110
  filter: _this2.state.filter,
110
111
  showDefaultThemeSelector: _this2.props.showDefaultThemeSelector,
111
112
  showLayerAfterChangeTheme: _this2.props.showLayerAfterChangeTheme
@@ -128,6 +129,8 @@ _defineProperty(ThemeSwitcher, "propTypes", {
128
129
  /** Whether to allow collapsing theme groups. */
129
130
  collapsibleGroups: PropTypes.bool,
130
131
  currentTask: PropTypes.object,
132
+ /** Whether to expand theme groups by default. */
133
+ expandGroups: PropTypes.bool,
131
134
  /** Whether to hide the add theme button. Note: the button will also be hidden if the global option `allowAddingOtherThemes` is `false`. */
132
135
  hideAddThemeButton: PropTypes.bool,
133
136
  /** Whether to hide the add theme layers button. Note: the button will also be hidden if the global option `allowAddingOtherThemes` is `false`. */
@@ -104,7 +104,7 @@ var EditingSupport = /*#__PURE__*/function (_React$Component) {
104
104
  _this.props.map.addInteraction(drawInteraction);
105
105
  _this.interaction = drawInteraction;
106
106
  _this.setState({
107
- showRecordLocation: ["Point", "LineString"].includes(geomType)
107
+ showRecordLocation: ["Point", "LineString", "MultiPoint", "MultiLineString"].includes(geomType)
108
108
  });
109
109
  });
110
110
  _defineProperty(_this, "addEditInteraction", function () {
@@ -199,7 +199,7 @@ var EditingSupport = /*#__PURE__*/function (_React$Component) {
199
199
  }, {
200
200
  key: "render",
201
201
  value: function render() {
202
- if (this.state.showRecordLocation) {
202
+ if (this.state.showRecordLocation && this.props.editContext.geomType) {
203
203
  var geomType = this.props.editContext.geomType.replace(/Z$/, '');
204
204
  return /*#__PURE__*/React.createElement(LocationRecorder, {
205
205
  drawInteraction: this.interaction,
@@ -376,7 +376,7 @@ var RedliningSupport = /*#__PURE__*/function (_React$Component) {
376
376
  _defineProperty(_this, "addTransformInteraction", function () {
377
377
  var redliningLayer = _this.searchRedliningLayer(_this.props.redlining.layer);
378
378
  if (!redliningLayer) {
379
- return;
379
+ return null;
380
380
  }
381
381
  var transformInteraction = _this.setupTransformInteraction(redliningLayer, [], true);
382
382
  transformInteraction.on('select', function (evt) {
@@ -390,6 +390,7 @@ var RedliningSupport = /*#__PURE__*/function (_React$Component) {
390
390
  _this.commitFeatures(removed, _this.props.redlining);
391
391
  });
392
392
  _this.picking = true;
393
+ return transformInteraction;
393
394
  });
394
395
  _defineProperty(_this, "maybeEnterTemporaryDrawMode", function (ev) {
395
396
  var redliningLayer = _this.searchRedliningLayer(_this.props.redlining.layer);
@@ -537,6 +538,45 @@ var RedliningSupport = /*#__PURE__*/function (_React$Component) {
537
538
  _this.selectedFeatures = [];
538
539
  }
539
540
  });
541
+ _defineProperty(_this, "cloneCurrentFeatures", function () {
542
+ if (isEmpty(_this.selectedFeatures)) {
543
+ return;
544
+ }
545
+ var _shiftCoordinates = function shiftCoordinates(coords) {
546
+ if (typeof coords[0] === 'number') {
547
+ coords[0] += 10;
548
+ coords[1] += 10;
549
+ } else {
550
+ coords.map(_shiftCoordinates);
551
+ }
552
+ };
553
+ var cloneIds = [];
554
+ var newFeatureObjs = _this.selectedFeatures.map(function (feature) {
555
+ _this.deselectFeature(feature, false);
556
+ var featureObj = _this.serializeFeature(feature);
557
+ featureObj.id = uuidv4();
558
+ _shiftCoordinates(featureObj.geometry.coordinates);
559
+ cloneIds.push(featureObj.id);
560
+ return featureObj;
561
+ });
562
+ _this.updateRedliningState(true);
563
+ var layer = {
564
+ id: _this.props.redlining.layer,
565
+ title: _this.props.redlining.layerTitle,
566
+ role: LayerRole.USERLAYER
567
+ };
568
+ _this.props.addLayerFeatures(layer, newFeatureObjs);
569
+ _this.waitForFeatureAndLayer(_this.props.redlining.layer, cloneIds[0], function (l) {
570
+ var features = cloneIds.map(function (id) {
571
+ return l.getSource().getFeatureById(id);
572
+ });
573
+ _this.selectFeatures(features);
574
+ while (_this.interactions.length > 0) {
575
+ _this.props.map.removeInteraction(_this.interactions.shift());
576
+ }
577
+ _this.addTransformInteraction().setSelection(new ol.Collection(features));
578
+ });
579
+ });
540
580
  _defineProperty(_this, "updateRedliningState", function (firstSelection) {
541
581
  if (_this.selectedFeatures.length > 0) {
542
582
  var features = _this.selectedFeatures;
@@ -574,17 +614,23 @@ var RedliningSupport = /*#__PURE__*/function (_React$Component) {
574
614
  });
575
615
  _this.updateRedliningState(firstSelection);
576
616
  });
617
+ _defineProperty(_this, "deselectFeature", function (feature, updateState) {
618
+ var styleName = feature.get("shape") === "Text" ? "text" : "default";
619
+ var style = FeatureStyles[styleName](feature, feature.get('styleOptions'));
620
+ feature.setStyle(style);
621
+ feature.un('change', _this.updateMeasurements);
622
+ _this.selectedFeatures = _this.selectedFeatures.filter(function (f) {
623
+ return f !== feature;
624
+ });
625
+ if (updateState) {
626
+ _this.updateRedliningState(false);
627
+ }
628
+ });
577
629
  _defineProperty(_this, "commitFeatures", function (features, redliningProps) {
578
630
  var newFeature = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
579
631
  var featureObjects = features.map(function (feature) {
580
632
  var _featureObj$geometry;
581
- var styleName = feature.get("shape") === "Text" ? "text" : "default";
582
- var style = FeatureStyles[styleName](feature, feature.get('styleOptions'));
583
- feature.setStyle(style);
584
- feature.un('change', _this.updateMeasurements);
585
- _this.selectedFeatures = _this.selectedFeatures.filter(function (f) {
586
- return f !== feature;
587
- });
633
+ _this.deselectFeature(feature, false);
588
634
  var featureObj = _this.serializeFeature(feature);
589
635
  // Don't commit empty/invalid features
590
636
  if (featureObj.shape === "Text" && !featureObj.properties.label || featureObj.shape === "Circle" && featureObj.circleParams.radius === 0 || ((_featureObj$geometry = featureObj.geometry) === null || _featureObj$geometry === void 0 ? void 0 : _featureObj$geometry.type) === "Polygon" && feature.getGeometry().getArea() === 0) {
@@ -631,12 +677,9 @@ var RedliningSupport = /*#__PURE__*/function (_React$Component) {
631
677
  _this.commitFeatures(_this.selectedFeatures, redliningProps, false);
632
678
  } else {
633
679
  _this.selectedFeatures.forEach(function (feature) {
634
- var styleName = feature.get("shape") === "Text" ? "text" : "default";
635
- var style = FeatureStyles[styleName](feature, feature.get('styleOptions'));
636
- feature.setStyle(style);
637
- feature.un('change', _this.updateMeasurements);
680
+ _this.deselectFeature(feature, false);
638
681
  });
639
- _this.selectedFeatures = [];
682
+ _this.updateRedliningState(false);
640
683
  }
641
684
  _this.props.map.un('click', _this.maybeEnterTemporaryDrawMode);
642
685
  _this.picking = false;
@@ -773,6 +816,13 @@ var RedliningSupport = /*#__PURE__*/function (_React$Component) {
773
816
  }));
774
817
  return;
775
818
  }
819
+ if (this.props.redlining.action === 'Clone') {
820
+ this.cloneCurrentFeatures();
821
+ this.props.changeRedliningState(_objectSpread(_objectSpread({}, prevProps.redlining), {}, {
822
+ selectedFeature: null
823
+ }));
824
+ return;
825
+ }
776
826
  if (this.props.redlining.action === 'Export') {
777
827
  this["export"]();
778
828
  this.props.changeRedliningState(_objectSpread(_objectSpread({}, prevProps.redlining), {}, {
@@ -40,3 +40,37 @@
40
40
  #Editing select.editing-feature-select:disabled {
41
41
  cursor: not-allowed;
42
42
  }
43
+
44
+ .editing-clone-dialog {
45
+ display: flex;
46
+ flex-direction: column;
47
+ gap: 0.25em;
48
+ }
49
+
50
+ .editing-clone-header {
51
+ font-weight: bold;
52
+ margin-bottom: 0.25em;
53
+ }
54
+
55
+ .editing-clone-table {
56
+ margin-top: calc(-0.25em - 1px);
57
+ background: var(--list-bg-color);
58
+ border: 1px solid var(--border-color);
59
+ width: 100%;
60
+ table-layout: fixed;
61
+ }
62
+
63
+ .editing-clone-table td {
64
+ padding: 0.125em 0.25em;
65
+ white-space: nowrap;
66
+ overflow: hidden;
67
+ text-overflow: ellipsis;
68
+ width: 50%;
69
+ }
70
+
71
+ .editing-clone-attribute {
72
+ display: flex;
73
+ align-items: center;
74
+ gap: 0.5em;
75
+ cursor: pointer;
76
+ }