qwc2 2026.4.23 → 2026.5.6

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 (45) hide show
  1. package/actions/theme.js +3 -2
  2. package/components/AutoEditForm.js +4 -3
  3. package/components/EditComboField.js +7 -1
  4. package/components/IdentifyViewer.js +3 -0
  5. package/components/PickFeature.js +2 -1
  6. package/components/widgets/DateTimeInput.js +1 -0
  7. package/package.json +2 -2
  8. package/plugins/API.js +1 -1
  9. package/plugins/Cyclomedia.js +53 -21
  10. package/plugins/Editing.js +1 -1
  11. package/plugins/Help.js +6 -1
  12. package/plugins/MapExport.js +6 -4
  13. package/plugins/MapFilter.js +5 -3
  14. package/plugins/Redlining.js +14 -5
  15. package/plugins/SensorThingsTool.js +41 -22
  16. package/plugins/map/EditingSupport.js +72 -30
  17. package/plugins/map3d/MapExport3D.js +1 -1
  18. package/plugins/style/SensorThingsTool.css +13 -5
  19. package/reducers/layers.js +6 -4
  20. package/reducers/task.js +6 -3
  21. package/static/translations/bg-BG.json +1 -0
  22. package/static/translations/ca-ES.json +1 -0
  23. package/static/translations/cs-CZ.json +1 -0
  24. package/static/translations/de-CH.json +1 -0
  25. package/static/translations/de-DE.json +1 -0
  26. package/static/translations/en-US.json +1 -0
  27. package/static/translations/es-ES.json +1 -0
  28. package/static/translations/fi-FI.json +1 -0
  29. package/static/translations/fr-FR.json +1 -0
  30. package/static/translations/hu-HU.json +1 -0
  31. package/static/translations/it-IT.json +1 -0
  32. package/static/translations/ja-JP.json +1 -0
  33. package/static/translations/nl-NL.json +1 -0
  34. package/static/translations/no-NO.json +1 -0
  35. package/static/translations/pl-PL.json +1 -0
  36. package/static/translations/pt-BR.json +1 -0
  37. package/static/translations/pt-PT.json +1 -0
  38. package/static/translations/ro-RO.json +1 -0
  39. package/static/translations/ru-RU.json +1 -0
  40. package/static/translations/sv-SE.json +1 -0
  41. package/static/translations/tr-TR.json +1 -0
  42. package/static/translations/tsconfig.json +1 -0
  43. package/static/translations/uk-UA.json +1 -0
  44. package/utils/EditingInterface.js +1 -1
  45. package/utils/LayerUtils.js +2 -1
package/actions/theme.js CHANGED
@@ -53,7 +53,7 @@ export function setThemeLayersList(theme) {
53
53
  };
54
54
  }
55
55
  export function finishThemeSetup(dispatch, theme, themes, layerConfigs, insertPos, permalinkLayers, externalLayerRestorer, visibleBgLayer, initialTheme, initialTask) {
56
- var _theme$config;
56
+ var _theme$config$section, _theme$config, _theme$config2;
57
57
  // Create layer
58
58
  var themeLayer = ThemeUtils.createThemeLayer(theme, themes);
59
59
  var layers = [themeLayer];
@@ -148,7 +148,8 @@ export function finishThemeSetup(dispatch, theme, themes, layerConfigs, insertPo
148
148
  type: SWITCHING_THEME,
149
149
  switching: false
150
150
  });
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);
151
+ var section = ConfigUtils.isMobile() ? "mobile" : "desktop";
152
+ var task = initialTask || ((_theme$config$section = theme === null || theme === void 0 || (_theme$config = theme.config) === null || _theme$config === void 0 || (_theme$config = _theme$config[section]) === null || _theme$config === void 0 ? void 0 : _theme$config.startupTask) !== null && _theme$config$section !== void 0 ? _theme$config$section : theme === null || theme === void 0 || (_theme$config2 = theme.config) === null || _theme$config2 === void 0 ? void 0 : _theme$config2.startupTask) || (initialTheme ? ConfigUtils.getConfigProp("startupTask") : null);
152
153
  if (task) {
153
154
  var mapClickAction = ConfigUtils.getPluginConfig(task.key).mapClickAction;
154
155
  dispatch(setCurrentTask(task.key, task.mode, mapClickAction, task.data));
@@ -50,7 +50,9 @@ var AutoEditForm = /*#__PURE__*/function (_React$Component) {
50
50
  }
51
51
  var input = null;
52
52
  var title = field.name + ":";
53
- if (field.type === "boolean" || field.type === "bool") {
53
+ if (constraints.hidden) {
54
+ return null;
55
+ } else if (field.type === "boolean" || field.type === "bool") {
54
56
  if (_this.props.touchFriendly) {
55
57
  var boolvalue = value === "1" || value === "on" || value === "true" || value === true;
56
58
  input = /*#__PURE__*/React.createElement(ToggleSwitch, _extends({
@@ -87,12 +89,11 @@ var AutoEditForm = /*#__PURE__*/function (_React$Component) {
87
89
  values: constraints.values
88
90
  }));
89
91
  } else if (field.type === "number") {
90
- var precision = constraints.step > 0 ? Math.ceil(-Math.log10(constraints.step)) : 6;
92
+ var precision = constraints.step > 0 ? Math.ceil(-Math.log10(constraints.step)) : 0;
91
93
  input = /*#__PURE__*/React.createElement(NumberInput, {
92
94
  decimals: precision,
93
95
  max: constraints.max,
94
96
  min: constraints.min,
95
- mobile: true,
96
97
  name: field.id,
97
98
  onChange: function onChange(nr) {
98
99
  return _this.props.updateField(field.id, nr);
@@ -186,7 +186,13 @@ var EditComboField = /*#__PURE__*/function (_React$Component) {
186
186
  key: "componentDidUpdate",
187
187
  value: function componentDidUpdate(prevProps) {
188
188
  var _this3 = this;
189
- if (this.props.keyvalrel && this.props.filterExpr !== prevProps.filterExpr) {
189
+ if (this.props.values && this.props.values !== prevProps.values) {
190
+ // This does not handle the case a selected value has disappeared from values, caller should handle that
191
+ this.setState({
192
+ values: this.props.values,
193
+ showPlaceholder: !this.hasEmptyValue(this.props.values)
194
+ });
195
+ } else if (this.props.keyvalrel && this.props.filterExpr !== prevProps.filterExpr) {
190
196
  var _this$props$filterExp3;
191
197
  KeyValCache.get(this.props.editIface, this.props.mapPrefix + "." + this.props.keyvalrel, (_this$props$filterExp3 = this.props.filterExpr) !== null && _this$props$filterExp3 !== void 0 ? _this$props$filterExp3 : null).then(function (values) {
192
198
  _this3.setState({
@@ -1155,6 +1155,9 @@ var IdentifyViewer = /*#__PURE__*/function (_React$Component) {
1155
1155
  var _window$qwc3;
1156
1156
  var formatter = _Object$values[_i];
1157
1157
  text = formatter(attrName, text, layer, result);
1158
+ if (/*#__PURE__*/React.isValidElement(text)) {
1159
+ return text;
1160
+ }
1158
1161
  }
1159
1162
  text = _this.props.attributeTransform(attrName, text, layer, result);
1160
1163
  text = MiscUtils.addLinkAnchors(text);
@@ -271,7 +271,8 @@ var PickFeature = /*#__PURE__*/function (_React$Component) {
271
271
  }
272
272
  }, entry.layer + ": " + ((_entry$feature$displa = entry.feature.displayname) !== null && _entry$feature$displa !== void 0 ? _entry$feature$displa : entry.feature.id));
273
273
  }) : /*#__PURE__*/React.createElement("div", {
274
- className: "pick-feature-menu-querying"
274
+ className: "pick-feature-menu-querying",
275
+ key: "spinner"
275
276
  }, /*#__PURE__*/React.createElement(Spinner, null), LocaleUtils.tr("pickfeature.querying")));
276
277
  }
277
278
  return [resultsMenu, /*#__PURE__*/React.createElement(MapSelection, {
@@ -54,6 +54,7 @@ var DateTimeInput = /*#__PURE__*/function (_React$Component) {
54
54
  value: function render() {
55
55
  var _this2 = this;
56
56
  var parts = (this.props.value || "T").split("T");
57
+ parts[1] = (parts[1] || "").replace(/\+[0-9:]+$/, ''); // Strip timezone
57
58
  parts[1] = (parts[1] || "").replace(/\.\d+$/, ''); // Strip milliseconds
58
59
  return /*#__PURE__*/React.createElement(InputContainer, {
59
60
  className: "DateTimeInput"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qwc2",
3
- "version": "2026.04.23",
3
+ "version": "2026.05.06",
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.1",
20
+ "@giro3d/giro3d": "^2.0.2",
21
21
  "@kayahr/text-encoding": "^2.1.0",
22
22
  "@loaders.gl/core": "^4.3.4",
23
23
  "@loaders.gl/shapefile": "^4.3.4",
package/plugins/API.js CHANGED
@@ -401,7 +401,7 @@ var API = /*#__PURE__*/function (_React$Component) {
401
401
  * * `name`: An identifier
402
402
  * * `fmtFunc`: The formatter function with signature `function(name, value, layer, feature)`
403
403
  *
404
- * The `fmtFunc` should return a string (which may also be a HTML fragment).
404
+ * The `fmtFunc` should return a string (which may also be a HTML fragment) or a React element.
405
405
  */
406
406
  _defineProperty(_this, "addIdentifyAttributeFormatter", function (name, fmtFunc) {
407
407
  window.qwc2.__attributeFormatters[name] = fmtFunc;
@@ -1,4 +1,8 @@
1
1
  function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
2
+ function _toConsumableArray(r) { return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _nonIterableSpread(); }
3
+ function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
4
+ function _iterableToArray(r) { if ("undefined" != typeof Symbol && null != r[Symbol.iterator] || null != r["@@iterator"]) return Array.from(r); }
5
+ function _arrayWithoutHoles(r) { if (Array.isArray(r)) return _arrayLikeToArray(r); }
2
6
  function _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); }
3
7
  function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
4
8
  function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
@@ -192,6 +196,10 @@ var Cyclomedia = /*#__PURE__*/function (_React$Component) {
192
196
  return "\n <!DOCTYPE html>\n <html>\n <head>\n <script nonce=\"".concat((_window$__CSP_NONCE__ = window.__CSP_NONCE__) !== null && _window$__CSP_NONCE__ !== void 0 ? _window$__CSP_NONCE__ : '', "\" type=\"text/javascript\" src=\"https://unpkg.com/react@18.3.1/umd/react.production.min.js\"></script>\n <script nonce=\"").concat((_window$__CSP_NONCE__2 = window.__CSP_NONCE__) !== null && _window$__CSP_NONCE__2 !== void 0 ? _window$__CSP_NONCE__2 : '', "\" type=\"text/javascript\" src=\"https://unpkg.com/react-dom@18.3.1/umd/react-dom.production.min.js\"></script>\n <script nonce=\"").concat((_window$__CSP_NONCE__3 = window.__CSP_NONCE__) !== null && _window$__CSP_NONCE__3 !== void 0 ? _window$__CSP_NONCE__3 : '', "\" type=\"text/javascript\" src=\"https://streetsmart.cyclomedia.com/api/v").concat(_this.props.cyclomediaVersion, "/StreetSmartApi.js\"></script>\n <script nonce=\"").concat((_window$__CSP_NONCE__4 = window.__CSP_NONCE__) !== null && _window$__CSP_NONCE__4 !== void 0 ? _window$__CSP_NONCE__4 : '', "\" type=\"text/javascript\">\n let apiInitialized = false;\n let initCallback = null;\n let posCallback = null;\n let measureCallback = null;\n\n function initApi() {\n StreetSmartApi.init({\n targetElement: document.getElementById(\"streetsmartApi\"),\n username: \"").concat(_this.state.username || undefined, "\",\n password: \"").concat(_this.state.password || undefined, "\",\n apiKey: \"").concat(_this.props.apikey, "\",\n clientId: \"").concat(_this.props.clientId, "\",\n loginOauth: ").concat(loginOauth, ",\n loginRedirectUri: \"").concat(_this.props.loginRedirectUri, "\",\n logoutRedirectUri: \"").concat(_this.props.logoutRedirectUri, "\",\n srs: \"").concat(_this.props.projection, "\",\n locale: \"").concat(lang, "\",\n configurationUrl: 'https://atlas.cyclomedia.com/configuration',\n addressSettings: {\n locale: \"us\",\n database: \"Nokia\"\n }\n }).then(() => {\n apiInitialized = true;\n if (initCallback) {\n initCallback(true);\n }\n }, (e) => {\n apiInitialized = false;\n if (initCallback) {\n initCallback(false, e.message);\n }\n });\n }\n function openImage(posStr, crs) {\n if (!apiInitialized) {\n return;\n }\n StreetSmartApi.open(posStr, {\n viewerType: StreetSmartApi.ViewerType.PANORAMA,\n srs: crs,\n panoramaViewer: {\n closable: false,\n maximizable: true,\n replace: true,\n recordingsVisible: true,\n navbarVisible: true,\n timeTravelVisible: true,\n measureTypeButtonVisible: true,\n measureTypeButtonStart: true,\n measureTypeButtonToggle: true,\n },\n }).then((result) => {\n if (result && result[0]){\n window.panoramaViewer = result[0];\n window.panoramaViewer.on(StreetSmartApi.Events.panoramaViewer.IMAGE_CHANGE, changeView);\n window.panoramaViewer.on(StreetSmartApi.Events.panoramaViewer.VIEW_CHANGE, changeView);\n StreetSmartApi.on(StreetSmartApi.Events.measurement.MEASUREMENT_CHANGED, changeMeasurement);\n StreetSmartApi.on(StreetSmartApi.Events.measurement.MEASUREMENT_STOPPED, stopMeasurement);\n }\n }).catch((reason) => {\n console.log('Failed to create component(s) through API: ' + reason);\n });\n }\n function changeView() {\n if (posCallback) {\n const recording = window.panoramaViewer.getRecording();\n const orientation = window.panoramaViewer.getOrientation();\n const pos = recording.xyz;\n const posData = {\n pos: [pos[0], pos[1]],\n crs: recording.srs,\n yaw: orientation.yaw * Math.PI / 180,\n hFov: orientation.hFov * Math.PI / 180.0\n }\n posCallback(posData);\n }\n }\n function changeMeasurement(e) {\n measureCallback(e.detail.activeMeasurement);\n }\n function stopMeasurement() {\n measureCallback(null);\n }\n function registerCallbacks(_initCallback, _posCallback, _measureCallback) {\n initCallback = _initCallback;\n posCallback = _posCallback;\n measureCallback = _measureCallback;\n }\n </script>\n <style>\n html, body, #streetsmartApi {height: 100%;}\n </style>\n </head>\n <body style=\"margin: 0\">\n <div id=\"streetsmartApi\">\n </div>\n </body>\n </html>\n ");
193
197
  });
194
198
  _defineProperty(_this, "addRecordingsWFS", function () {
199
+ // Maximum tile size in map CRS units to avoid exceeding the feature limit per request.
200
+ // ~1000 m in metric CRS, ~0.01° (≈ 1000 m) in geographic CRS, ~3280 ft in imperial CRS.
201
+ var crsUnits = CoordinatesUtils.getUnits(_this.props.mapCrs);
202
+ var MAX_TILE_SIZE = crsUnits === 'degrees' ? 0.01 : crsUnits === 'ft' ? 3280 : 1000;
195
203
  var layer = {
196
204
  id: 'cyclomedia-recordings',
197
205
  type: 'wfs',
@@ -202,38 +210,62 @@ var Cyclomedia = /*#__PURE__*/function (_React$Component) {
202
210
  miny = _CoordinatesUtils$rep2[1],
203
211
  maxx = _CoordinatesUtils$rep2[2],
204
212
  maxy = _CoordinatesUtils$rep2[3];
205
- // Cyclomedia WFS only returns up to 3000 points per request. Split bbox in four to reduce chance of hitting the limit
206
- var midx = 0.5 * (minx + maxx);
207
- var midy = 0.5 * (miny + maxy);
208
- var bboxes = [[minx, miny, midx, midy],
209
- // Bottom left
210
- [midx, miny, maxx, midy],
211
- // Bottom right
212
- [midx, midy, maxx, maxy],
213
- // Top right
214
- [minx, midy, midx, maxy] // Top left
215
- ];
216
- bboxes.forEach(function (bbox) {
217
- var bboxstr = bbox.join(",");
218
- var reqUrl = "https://atlasapi.cyclomedia.com/api/recording/wfs?service=WFS&version=1.1.0&request=GetFeature&typename=atlas:Recording&srsname=".concat(_this.props.mapCrs, "&bbox=").concat(bboxstr, "&maxFeatures=10000000");
213
+ var width = maxx - minx;
214
+ var height = maxy - miny;
215
+ // Cyclomedia WFS only returns up to 3000 points per request.
216
+ // Split the bbox into tiles so that no tile exceeds MAX_TILE_SIZE in each dimension.
217
+ // Each tile is expanded by OVERLAP on every side so that features on tile borders
218
+ // are reliably included. Duplicate features are removed afterwards via their id.
219
+ var OVERLAP = MAX_TILE_SIZE * 0.05; // 5 % of tile size
220
+ var cols = Math.max(1, Math.ceil(width / MAX_TILE_SIZE));
221
+ var rows = Math.max(1, Math.ceil(height / MAX_TILE_SIZE));
222
+ var tileW = width / cols;
223
+ var tileH = height / rows;
224
+ var tiles = [];
225
+ for (var row = 0; row < rows; row++) {
226
+ for (var col = 0; col < cols; col++) {
227
+ tiles.push([minx + col * tileW - OVERLAP, miny + row * tileH - OVERLAP, minx + (col + 1) * tileW + OVERLAP, miny + (row + 1) * tileH + OVERLAP]);
228
+ }
229
+ }
230
+ var authHeader = "Basic " + btoa(_this.state.username + ":" + _this.state.password);
231
+ var onError = function onError() {
232
+ vectorSource.removeLoadedExtent(extent);
233
+ failure();
234
+ };
235
+ var completed = 0;
236
+ var hasError = false;
237
+ var allFeatures = [];
238
+ tiles.forEach(function (tile) {
239
+ var bboxstr = tile.join(",");
240
+ var reqUrl = "https://atlasapi.cyclomedia.com/api/recording/wfs?service=WFS&version=1.1.0&request=GetFeature&typename=atlas:Recording&srsname=".concat(_this.props.mapCrs, "&bbox=").concat(bboxstr, "&maxFeatures=10000");
219
241
  var xhr = new XMLHttpRequest();
220
242
  xhr.open('GET', reqUrl);
221
- xhr.setRequestHeader("Authorization", "Basic " + btoa(_this.state.username + ":" + _this.state.password));
222
- var onError = function onError() {
223
- vectorSource.removeLoadedExtent(extent);
224
- failure();
243
+ xhr.setRequestHeader("Authorization", authHeader);
244
+ xhr.onerror = function () {
245
+ if (!hasError) {
246
+ hasError = true;
247
+ onError();
248
+ }
225
249
  };
226
- xhr.onerror = onError;
227
250
  xhr.onload = function () {
251
+ if (hasError) {
252
+ return;
253
+ }
228
254
  if (xhr.status === 200) {
229
255
  var features = vectorSource.getFormat().readFeatures(xhr.responseText, {
230
256
  dataProjection: _this.props.mapCrs,
231
257
  featureProjection: projection.getCode()
232
258
  });
233
- vectorSource.addFeatures(features);
234
- success(features);
259
+ allFeatures.push.apply(allFeatures, _toConsumableArray(features));
235
260
  } else {
261
+ hasError = true;
236
262
  onError();
263
+ return;
264
+ }
265
+ completed++;
266
+ if (completed === tiles.length) {
267
+ vectorSource.addFeatures(allFeatures);
268
+ success(allFeatures);
237
269
  }
238
270
  };
239
271
  xhr.send();
@@ -532,7 +532,7 @@ var Editing = /*#__PURE__*/function (_React$Component) {
532
532
  if (match) {
533
533
  var oldvisibility = match.sublayer.visibility;
534
534
  if (oldvisibility !== visibility && visibility !== null) {
535
- var recurseDirection = !oldvisibility ? "both" : "children";
535
+ var recurseDirection = !oldvisibility ? "parents" : null;
536
536
  _this.props.changeLayerProperty(match.layer.id, "visibility", visibility, match.path, recurseDirection);
537
537
  }
538
538
  return oldvisibility;
package/plugins/Help.js CHANGED
@@ -60,8 +60,13 @@ var Help = /*#__PURE__*/function (_React$Component) {
60
60
  return _createClass(Help, [{
61
61
  key: "componentDidMount",
62
62
  value: function componentDidMount() {
63
+ this.componentDidUpdate({});
64
+ }
65
+ }, {
66
+ key: "componentDidUpdate",
67
+ value: function componentDidUpdate(prevProps) {
63
68
  var _this2 = this;
64
- if (this.props.bodyContentsFragmentUrl) {
69
+ if (this.props.bodyContentsFragmentUrl && this.props.bodyContentsFragmentUrl !== prevProps.bodyContentsFragmentUrl) {
65
70
  axios.get(this.props.bodyContentsFragmentUrl).then(function (response) {
66
71
  _this2.setState({
67
72
  body: response.data.replace('$VERSION$', process.env.BuildDate)
@@ -351,6 +351,7 @@ var MapExport = /*#__PURE__*/function (_React$Component) {
351
351
  params.WIDTH = width;
352
352
  params.HEIGHT = height;
353
353
  params.filename = fileName;
354
+ params.FILTER_GEOM = VectorLayerUtils.geoJSONGeomToWkt(VectorLayerUtils.reprojectGeometry(_this.props.filter.filterGeom, _this.props.map.projection, crs));
354
355
 
355
356
  // Dimension values
356
357
  _this.props.layers.forEach(function (layer) {
@@ -464,15 +465,14 @@ var MapExport = /*#__PURE__*/function (_React$Component) {
464
465
  var _layer$mapFormats;
465
466
  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
467
  }).reverse().map(function (layer) {
467
- var _layer$params$FILTER, _layer$params$FILTER_;
468
+ var _layer$params$FILTER;
468
469
  return {
469
470
  layer: layer,
470
471
  params: _objectSpread(_objectSpread({}, baseParams), {}, {
471
472
  LAYERS: layer.params.LAYERS,
472
473
  OPACITIES: layer.params.OPACITIES,
473
474
  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_ : ''
475
+ FILTER: (_layer$params$FILTER = layer.params.FILTER) !== null && _layer$params$FILTER !== void 0 ? _layer$params$FILTER : ''
476
476
  })
477
477
  };
478
478
  });
@@ -596,6 +596,7 @@ _defineProperty(MapExport, "propTypes", {
596
596
  exportExternalLayers: PropTypes.bool,
597
597
  /** Template for the name of the generated files when downloading. Can contain the placeholders `{username}`, `{tenant}`, `{theme}`, `{themeTitle}`, `{timestamp}`. */
598
598
  fileNameTemplate: PropTypes.string,
599
+ filter: PropTypes.object,
599
600
  /** Formats to force as available even if the map capabilities report otherwise. Useful if a serviceUrl is defined in a format configuration. */
600
601
  forceAvailableFormats: PropTypes.array,
601
602
  /** 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.
@@ -640,7 +641,8 @@ var selector = function selector(state) {
640
641
  return {
641
642
  theme: state.theme.current,
642
643
  map: state.map,
643
- layers: state.layers.flat
644
+ layers: state.layers.flat,
645
+ filter: state.layers.filter
644
646
  };
645
647
  };
646
648
  export default connect(selector, {
@@ -383,8 +383,10 @@ var MapFilter = /*#__PURE__*/function (_React$Component) {
383
383
  });
384
384
  _defineProperty(_this, "renderPredefinedFilters", function () {
385
385
  var predefinedFilters = _this.collectPredefinedFilters(_this.props.layers);
386
- return Object.values(predefinedFilters).map(function (config) {
387
- var _config$title, _this$state$filters$c;
386
+ return Object.values(predefinedFilters).filter(function (filter) {
387
+ return filter.id in _this.state.filters;
388
+ }).map(function (config) {
389
+ var _config$title;
388
390
  return /*#__PURE__*/React.createElement("div", {
389
391
  className: "map-filter-entry",
390
392
  key: config.id
@@ -393,7 +395,7 @@ var MapFilter = /*#__PURE__*/function (_React$Component) {
393
395
  }, /*#__PURE__*/React.createElement("span", {
394
396
  className: "map-filter-entry-title"
395
397
  }, (_config$title = config.title) !== null && _config$title !== void 0 ? _config$title : LocaleUtils.tr(config.titlemsgid)), /*#__PURE__*/React.createElement(ToggleSwitch, {
396
- active: (_this$state$filters$c = _this.state.filters[config.id]) === null || _this$state$filters$c === void 0 ? void 0 : _this$state$filters$c.active,
398
+ active: _this.state.filters[config.id].active,
397
399
  onChange: function onChange(active) {
398
400
  return _this.toggleFilter(config.id, active);
399
401
  }
@@ -35,7 +35,7 @@ function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e
35
35
  import React from 'react';
36
36
  import { connect } from 'react-redux';
37
37
  import PropTypes from 'prop-types';
38
- import { LayerRole, addLayer } from '../actions/layers';
38
+ import { LayerRole, addLayer, changeLayerProperty } from '../actions/layers';
39
39
  import { setSnappingConfig } from '../actions/map';
40
40
  import { changeRedliningState, resetRedliningState } from '../actions/redlining';
41
41
  import Icon from '../components/Icon';
@@ -70,13 +70,18 @@ var Redlining = /*#__PURE__*/function (_React$Component) {
70
70
  geomType: (_data$geomType = data === null || data === void 0 ? void 0 : data.geomType) !== null && _data$geomType !== void 0 ? _data$geomType : null
71
71
  }, _this.redliningStateDefaults()));
72
72
  _this.props.setSnappingConfig(_this.props.snapping, _this.props.snappingActive);
73
+ var layer = null;
73
74
  if (data && data.layerId) {
74
- var layer = _this.props.layers.find(function (l) {
75
+ layer = _this.props.layers.find(function (l) {
75
76
  return l.id === data.layerId;
76
77
  });
77
- if (layer) {
78
- _this.changeRedliningLayer(layer);
79
- }
78
+ } else {
79
+ layer = _this.props.layers.find(function (l) {
80
+ return l.id === _this.props.redlining.layer;
81
+ });
82
+ }
83
+ if (layer) {
84
+ _this.changeRedliningLayer(layer);
80
85
  }
81
86
  });
82
87
  _defineProperty(_this, "onHide", function () {
@@ -550,6 +555,8 @@ var Redlining = /*#__PURE__*/function (_React$Component) {
550
555
  layerTitle: layer.title,
551
556
  action: action
552
557
  });
558
+ // Ensure layer is visible
559
+ _this.props.changeLayerProperty(layer.id, "visibility", true);
553
560
  });
554
561
  _this.labelInput = null;
555
562
  _this.dashIcons = {};
@@ -617,6 +624,7 @@ _defineProperty(Redlining, "propTypes", {
617
624
  addLayer: PropTypes.func,
618
625
  /** Whether to allow labeling geometric figures. */
619
626
  allowGeometryLabels: PropTypes.bool,
627
+ changeLayerProperty: PropTypes.func,
620
628
  changeRedliningState: PropTypes.func,
621
629
  /** Default area unit. Options: `metric`, `imperial`, `sqm`, `ha`, `sqkm`, `sqft`, `acre`, `sqmi` */
622
630
  defaultAreaUnit: PropTypes.string,
@@ -675,6 +683,7 @@ export default (function (plugins) {
675
683
  };
676
684
  }, {
677
685
  changeRedliningState: changeRedliningState,
686
+ changeLayerProperty: changeLayerProperty,
678
687
  addLayer: addLayer,
679
688
  resetRedliningState: resetRedliningState,
680
689
  setSnappingConfig: setSnappingConfig
@@ -235,9 +235,9 @@ var SensorThingsTool = /*#__PURE__*/function (_React$Component) {
235
235
  showLocationInfoWindow: false,
236
236
  // currently selected Datastreams filter options
237
237
  currentDatastreamsFilter: {
238
- thingId: -1,
239
- sensorId: -1,
240
- observedPropertyId: -1
238
+ thingId: '-1',
239
+ sensorId: '-1',
240
+ observedPropertyId: '-1'
241
241
  },
242
242
  // show Datastreams filter window for currently selected Location if true
243
243
  showDatastreamsFilterWindow: false,
@@ -666,7 +666,7 @@ var SensorThingsTool = /*#__PURE__*/function (_React$Component) {
666
666
  locationSelect = /*#__PURE__*/React.createElement("div", null, LocaleUtils.tr("sensorthingstool.locationLabel"), ":\xA0", /*#__PURE__*/React.createElement("select", {
667
667
  onChange: function onChange(ev) {
668
668
  return _this.setState({
669
- currentLocationId: parseInt(ev.target.value, 10)
669
+ currentLocationId: ev.target.value
670
670
  });
671
671
  },
672
672
  value: _this.state.currentLocationId
@@ -693,14 +693,14 @@ var SensorThingsTool = /*#__PURE__*/function (_React$Component) {
693
693
  icon: "trash"
694
694
  }));
695
695
  if (_this.state.currentSensorLocation !== null) {
696
- var datastreamsFilterActive = _this.state.currentDatastreamsFilter.thingId !== -1 || _this.state.currentDatastreamsFilter.sensorId !== -1 || _this.state.currentDatastreamsFilter.observedPropertyId !== -1;
696
+ var datastreamsFilterActive = _this.state.currentDatastreamsFilter.thingId !== '-1' || _this.state.currentDatastreamsFilter.sensorId !== '-1' || _this.state.currentDatastreamsFilter.observedPropertyId !== '-1';
697
697
  if (_this.state.currentSensorLocation.filteredDatastreams.length > 0) {
698
698
  datastreamSelect = /*#__PURE__*/React.createElement("div", {
699
699
  className: "sensor-things-location-datastreams"
700
700
  }, LocaleUtils.tr("sensorthingstool.datastreamLabel"), ":\xA0", /*#__PURE__*/React.createElement("select", {
701
701
  onChange: function onChange(ev) {
702
702
  return _this.setState({
703
- currentDatastreamId: parseInt(ev.target.value, 10)
703
+ currentDatastreamId: ev.target.value
704
704
  });
705
705
  },
706
706
  value: _this.state.currentDatastreamId
@@ -929,14 +929,30 @@ var SensorThingsTool = /*#__PURE__*/function (_React$Component) {
929
929
  // show popup with list of Locations at picking pos, cf. MapInfoTooltip
930
930
  var pixel = MapUtils.getHook(MapUtils.GET_PIXEL_FROM_COORDINATES_HOOK)(_this.state.pickGeom.coordinates);
931
931
  var style = {
932
- left: pixel[0] + 16 + "px",
932
+ left: pixel[0] + 32 + "px",
933
933
  top: pixel[1] + "px"
934
934
  };
935
935
  return /*#__PURE__*/React.createElement("div", {
936
936
  className: "sensor-things-location-select",
937
937
  key: "SensorThingsLocationSelectPopup",
938
938
  style: style
939
- }, /*#__PURE__*/React.createElement("b", null, LocaleUtils.tr("sensorthingstool.selectLocation"), ":"), /*#__PURE__*/React.createElement("br", null), _this.state.locationsAtPoint.map(function (location, idx) {
939
+ }, /*#__PURE__*/React.createElement("div", {
940
+ className: "sensor-things-location-select-header"
941
+ }, /*#__PURE__*/React.createElement("b", null, LocaleUtils.tr("sensorthingstool.selectLocation")), /*#__PURE__*/React.createElement("div", {
942
+ className: "sensor-things-toolbar-spacer"
943
+ }), /*#__PURE__*/React.createElement("button", {
944
+ className: "button",
945
+ onClick: function onClick() {
946
+ return _this.setState({
947
+ locationsAtPoint: []
948
+ });
949
+ },
950
+ title: LocaleUtils.tr("common.close")
951
+ }, /*#__PURE__*/React.createElement(Icon, {
952
+ icon: "remove"
953
+ }))), /*#__PURE__*/React.createElement("div", {
954
+ className: "sensor-things-location-select-list"
955
+ }, _this.state.locationsAtPoint.map(function (location, idx) {
940
956
  return /*#__PURE__*/React.createElement("div", {
941
957
  key: "select-location-" + idx,
942
958
  onClickCapture: function onClickCapture() {
@@ -953,7 +969,7 @@ var SensorThingsTool = /*#__PURE__*/function (_React$Component) {
953
969
  });
954
970
  }
955
971
  }, location.name, ": ", location.description);
956
- }));
972
+ })));
957
973
  } else {
958
974
  return null;
959
975
  }
@@ -1053,9 +1069,9 @@ var SensorThingsTool = /*#__PURE__*/function (_React$Component) {
1053
1069
  currentLocationId: nextSelectedLocationId,
1054
1070
  currentSensorLocation: null,
1055
1071
  currentDatastreamsFilter: {
1056
- thingId: -1,
1057
- sensorId: -1,
1058
- observedPropertyId: -1
1072
+ thingId: '-1',
1073
+ sensorId: '-1',
1074
+ observedPropertyId: '-1'
1059
1075
  },
1060
1076
  currentDatastreamId: null,
1061
1077
  datastreams: nextDatastreams,
@@ -1075,7 +1091,7 @@ var SensorThingsTool = /*#__PURE__*/function (_React$Component) {
1075
1091
  _defineProperty(_this, "updateDatastreamsFilter", function (field, value) {
1076
1092
  _this.setState(function (state) {
1077
1093
  return {
1078
- currentDatastreamsFilter: _objectSpread(_objectSpread({}, state.currentDatastreamsFilter), {}, _defineProperty({}, field, parseInt(value, 10)))
1094
+ currentDatastreamsFilter: _objectSpread(_objectSpread({}, state.currentDatastreamsFilter), {}, _defineProperty({}, field, value))
1079
1095
  };
1080
1096
  });
1081
1097
  });
@@ -2236,9 +2252,9 @@ var SensorThingsTool = /*#__PURE__*/function (_React$Component) {
2236
2252
  filteredDatastreams: datastreamIds
2237
2253
  },
2238
2254
  currentDatastreamsFilter: {
2239
- thingId: -1,
2240
- sensorId: -1,
2241
- observedPropertyId: -1
2255
+ thingId: '-1',
2256
+ sensorId: '-1',
2257
+ observedPropertyId: '-1'
2242
2258
  },
2243
2259
  currentDatastreamId: datastreamIds[0],
2244
2260
  datastreams: _objectSpread(_objectSpread({}, state.datastreams), datastreamsLookup)
@@ -2252,22 +2268,25 @@ var SensorThingsTool = /*#__PURE__*/function (_React$Component) {
2252
2268
  _defineProperty(_this, "filterLocationDatastreams", function () {
2253
2269
  if (_this.state.currentSensorLocation !== null) {
2254
2270
  var filteredDatastreams = _this.state.currentSensorLocation.datastreams;
2255
- if (_this.state.currentDatastreamsFilter.thingId !== -1) {
2271
+
2272
+ // NOTE: entity IDs may be integer or UUID strings, so compare them as string
2273
+
2274
+ if (_this.state.currentDatastreamsFilter.thingId !== '-1') {
2256
2275
  // filter by Thing
2257
2276
  filteredDatastreams = filteredDatastreams.filter(function (datastreamId) {
2258
- return _this.state.datastreams[datastreamId].thing.id === _this.state.currentDatastreamsFilter.thingId;
2277
+ return _this.state.datastreams[datastreamId].thing.id.toString() === _this.state.currentDatastreamsFilter.thingId;
2259
2278
  });
2260
2279
  }
2261
- if (_this.state.currentDatastreamsFilter.sensorId !== -1) {
2280
+ if (_this.state.currentDatastreamsFilter.sensorId !== '-1') {
2262
2281
  // filter by Sensor
2263
2282
  filteredDatastreams = filteredDatastreams.filter(function (datastreamId) {
2264
- return _this.state.datastreams[datastreamId].sensor.id === _this.state.currentDatastreamsFilter.sensorId;
2283
+ return _this.state.datastreams[datastreamId].sensor.id.toString() === _this.state.currentDatastreamsFilter.sensorId;
2265
2284
  });
2266
2285
  }
2267
- if (_this.state.currentDatastreamsFilter.observedPropertyId !== -1) {
2286
+ if (_this.state.currentDatastreamsFilter.observedPropertyId !== '-1') {
2268
2287
  // filter by ObservedProperty
2269
2288
  filteredDatastreams = filteredDatastreams.filter(function (datastreamId) {
2270
- return _this.state.datastreams[datastreamId].observedProperty.id === _this.state.currentDatastreamsFilter.observedPropertyId;
2289
+ return _this.state.datastreams[datastreamId].observedProperty.id.toString() === _this.state.currentDatastreamsFilter.observedPropertyId;
2271
2290
  });
2272
2291
  }
2273
2292
  _this.setState(function (state) {
@@ -37,7 +37,9 @@ import { setEditContext } from '../../actions/editing';
37
37
  import LocationRecorder from '../../components/LocationRecorder';
38
38
  import MeasureSwitcher from '../../components/MeasureSwitcher';
39
39
  import { BottomToolPortalContext } from '../../components/PluginsContainer';
40
+ import ButtonBar from '../../components/widgets/ButtonBar';
40
41
  import FeatureStyles from "../../utils/FeatureStyles";
42
+ import LocaleUtils from '../../utils/LocaleUtils';
41
43
  import MeasureUtils from '../../utils/MeasureUtils';
42
44
 
43
45
  /**
@@ -49,6 +51,7 @@ var EditingSupport = /*#__PURE__*/function (_React$Component) {
49
51
  _classCallCheck(this, EditingSupport);
50
52
  _this = _callSuper(this, EditingSupport, [props]);
51
53
  _defineProperty(_this, "state", {
54
+ activeEditTool: 'Node',
52
55
  showRecordLocation: false,
53
56
  measurements: {
54
57
  showmeasurements: false,
@@ -56,6 +59,11 @@ var EditingSupport = /*#__PURE__*/function (_React$Component) {
56
59
  areaUnit: 'metric'
57
60
  }
58
61
  });
62
+ _defineProperty(_this, "setEditMode", function (action) {
63
+ _this.setState({
64
+ activeEditTool: action
65
+ }, _this.setEditInteraction);
66
+ });
59
67
  _defineProperty(_this, "changeMeasurementState", function (diff) {
60
68
  _this.setState(function (state) {
61
69
  return {
@@ -95,7 +103,7 @@ var EditingSupport = /*#__PURE__*/function (_React$Component) {
95
103
  }
96
104
  _this.currentLayer = _this.layers[_this.props.editContext.id];
97
105
  });
98
- _defineProperty(_this, "addDrawInteraction", function () {
106
+ _defineProperty(_this, "setDrawInteraction", function () {
99
107
  _this.reset();
100
108
  _this.setCurrentLayer();
101
109
  var geomType = _this.props.editContext.geomType.replace(/Z$/, '');
@@ -126,7 +134,7 @@ var EditingSupport = /*#__PURE__*/function (_React$Component) {
126
134
  showRecordLocation: ["Point", "LineString", "MultiPoint", "MultiLineString"].includes(geomType)
127
135
  });
128
136
  });
129
- _defineProperty(_this, "addEditInteraction", function () {
137
+ _defineProperty(_this, "setEditInteraction", function () {
130
138
  var _this$props$editConte;
131
139
  _this.reset();
132
140
  _this.setCurrentLayer();
@@ -135,26 +143,42 @@ var EditingSupport = /*#__PURE__*/function (_React$Component) {
135
143
  _this.currentFeature.on('change', _this.updateMeasurements);
136
144
  _this.updateMeasurements();
137
145
  _this.currentLayer.getSource().addFeature(_this.currentFeature);
138
- var modifyInteraction = new ol.interaction.Modify({
139
- features: new ol.Collection([_this.currentFeature]),
140
- condition: function condition(event) {
141
- return event.originalEvent.buttons === 1;
142
- },
143
- deleteCondition: function deleteCondition(event) {
144
- // delete vertices on SHIFT + click
145
- if (event.type === "pointerdown" && ol.events.condition.shiftKeyOnly(event)) {
146
- _this.props.map.setIgnoreNextClick(true);
147
- }
148
- return ol.events.condition.shiftKeyOnly(event) && ol.events.condition.singleClick(event);
149
- },
150
- style: FeatureStyles.sketchInteraction()
151
- });
152
- modifyInteraction.on('modifyend', function () {
153
- _this.commitCurrentFeature();
154
- }, _this);
155
- modifyInteraction.setActive(_this.props.editContext.geomType && _this.props.editContext.permissions.updatable && ((_this$props$editConte = _this.props.editContext.editConfig) === null || _this$props$editConte === void 0 || (_this$props$editConte = _this$props$editConte.permissions) === null || _this$props$editConte === void 0 ? void 0 : _this$props$editConte.updatable) === true && !_this.props.editContext.geomReadOnly && !_this.props.editContext.geomNonZeroZ);
156
- _this.props.map.addInteraction(modifyInteraction);
157
- _this.interaction = modifyInteraction;
146
+ if (_this.state.activeEditTool === "Node") {
147
+ var modifyInteraction = new ol.interaction.Modify({
148
+ features: new ol.Collection([_this.currentFeature]),
149
+ condition: function condition(event) {
150
+ return event.originalEvent.buttons === 1;
151
+ },
152
+ deleteCondition: function deleteCondition(event) {
153
+ // delete vertices on SHIFT + click
154
+ if (event.type === "pointerdown" && ol.events.condition.shiftKeyOnly(event)) {
155
+ _this.props.map.setIgnoreNextClick(true);
156
+ }
157
+ return ol.events.condition.shiftKeyOnly(event) && ol.events.condition.singleClick(event);
158
+ },
159
+ style: FeatureStyles.sketchInteraction()
160
+ });
161
+ modifyInteraction.on('modifyend', _this.commitCurrentFeature);
162
+ _this.interaction = modifyInteraction;
163
+ _this.props.map.addInteraction(_this.interaction);
164
+ } else if (_this.state.activeEditTool === "Transform") {
165
+ var transformInteraction = new ol.interaction.Transform({
166
+ stretch: false,
167
+ keepAspectRatio: function keepAspectRatio(ev) {
168
+ return ol.events.condition.shiftKeyOnly(ev);
169
+ },
170
+ layers: [_this.currentLayer],
171
+ translateFeature: true,
172
+ selection: false
173
+ });
174
+ transformInteraction.on('rotateend', _this.commitCurrentFeature);
175
+ transformInteraction.on('translateend', _this.commitCurrentFeature);
176
+ transformInteraction.on('scaleend', _this.commitCurrentFeature);
177
+ _this.interaction = transformInteraction;
178
+ _this.props.map.addInteraction(_this.interaction);
179
+ _this.interaction.setSelection([_this.currentFeature]);
180
+ }
181
+ _this.interaction.setActive(_this.props.editContext.geomType && _this.props.editContext.permissions.updatable && ((_this$props$editConte = _this.props.editContext.editConfig) === null || _this$props$editConte === void 0 || (_this$props$editConte = _this$props$editConte.permissions) === null || _this$props$editConte === void 0 ? void 0 : _this$props$editConte.updatable) === true && !_this.props.editContext.geomReadOnly && !_this.props.editContext.geomNonZeroZ);
158
182
  });
159
183
  _defineProperty(_this, "updateMeasurements", function () {
160
184
  var _this$state$measureme;
@@ -237,16 +261,16 @@ var EditingSupport = /*#__PURE__*/function (_React$Component) {
237
261
  } else if (curContext.action === 'Pick' && curContext.feature) {
238
262
  // If a feature without geometry was picked, enter draw mode, otherwise enter edit mode
239
263
  if (!curContext.feature.geometry && curContext.geomType) {
240
- this.addDrawInteraction();
264
+ this.setDrawInteraction();
241
265
  } else {
242
- this.addEditInteraction();
266
+ this.setEditInteraction();
243
267
  }
244
268
  } else if (curContext.action === 'Draw' && curContext.geomType) {
245
269
  // Usually, draw mode starts without a feature, but draw also can start with a pre-set geometry
246
270
  if (!(curContext.feature || {}).geometry || prevContext.id === curContext.id && prevContext.geomType !== curContext.geomType) {
247
- this.addDrawInteraction();
271
+ this.setDrawInteraction();
248
272
  } else if ((curContext.feature || {}).geometry) {
249
- this.addEditInteraction();
273
+ this.setEditInteraction();
250
274
  }
251
275
  } else {
252
276
  this.reset();
@@ -261,9 +285,28 @@ var EditingSupport = /*#__PURE__*/function (_React$Component) {
261
285
  }, {
262
286
  key: "render",
263
287
  value: function render() {
264
- var _this$props$editConte2;
288
+ var _this$props$editConte2, _this$props$editConte3;
289
+ var toolbar = null;
265
290
  var locationRecorder = null;
266
291
  var measureSwitcher = null;
292
+ if ((_this$props$editConte2 = this.props.editContext.feature) !== null && _this$props$editConte2 !== void 0 && _this$props$editConte2.geometry) {
293
+ var editButtons = [{
294
+ key: "Node",
295
+ tooltip: LocaleUtils.tr("redlining.draw"),
296
+ icon: "nodetool"
297
+ }, {
298
+ key: "Transform",
299
+ tooltip: LocaleUtils.tr("redlining.transform"),
300
+ icon: "transformtool"
301
+ }];
302
+ toolbar = /*#__PURE__*/ReactDOM.createPortal(/*#__PURE__*/React.createElement(ButtonBar, {
303
+ active: this.state.activeEditTool,
304
+ buttons: editButtons,
305
+ key: "ButtonBar",
306
+ onClick: this.setEditMode,
307
+ tooltipPos: "top"
308
+ }), this.context);
309
+ }
267
310
  if (this.state.showRecordLocation && this.props.editContext.geomType) {
268
311
  var geomType = this.props.editContext.geomType.replace(/Z$/, '');
269
312
  locationRecorder = /*#__PURE__*/React.createElement(LocationRecorder, {
@@ -273,16 +316,15 @@ var EditingSupport = /*#__PURE__*/function (_React$Component) {
273
316
  map: this.props.map
274
317
  });
275
318
  }
276
- if (this.props.editContext.action === "Draw" || (_this$props$editConte2 = this.props.editContext.feature) !== null && _this$props$editConte2 !== void 0 && _this$props$editConte2.geometry) {
319
+ if (this.props.editContext.action === "Draw" || (_this$props$editConte3 = this.props.editContext.feature) !== null && _this$props$editConte3 !== void 0 && _this$props$editConte3.geometry) {
277
320
  measureSwitcher = /*#__PURE__*/ReactDOM.createPortal(/*#__PURE__*/React.createElement(MeasureSwitcher, {
278
321
  changeMeasureState: this.changeMeasurementState,
279
322
  geomType: this.props.editContext.geomType,
280
- iconSize: "large",
281
323
  key: "MeasureSwitcher",
282
324
  measureState: this.state.measurements
283
325
  }), this.context);
284
326
  }
285
- return [measureSwitcher, locationRecorder];
327
+ return [toolbar, measureSwitcher, locationRecorder];
286
328
  }
287
329
  }]);
288
330
  }(React.Component);
@@ -352,7 +352,7 @@ var MapExport3D = /*#__PURE__*/function (_React$Component) {
352
352
  var exportScale = _this.state.exportScaleFactor / 100;
353
353
  if (_this.state.selectedFormat === "application/pdf") {
354
354
  var mapWidthMM = _this.state.layout.map.width;
355
- var exportWidthPx = _this.state.width;
355
+ var exportWidthPx = _this.state.frame.width;
356
356
  exportScale = Math.min(5, _this.state.exportDpi / (exportWidthPx * 25.4 / mapWidthMM));
357
357
  }
358
358
  _this.setState({
@@ -150,18 +150,26 @@ table.sensor-things-datastream-statistics-table td.sensor-things-datastream-stat
150
150
  }
151
151
 
152
152
  div.sensor-things-location-select {
153
- max-height: 16em;
154
- overflow-y: auto;
155
- padding: 0.25em;
156
153
  position: fixed;
157
154
  background-color: var(--container-bg-color);
158
155
  box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.75);
159
156
  z-index: 2;
160
157
  }
161
- div.sensor-things-location-select div {
158
+ div.sensor-things-location-select-header {
159
+ display: flex;
160
+ padding-left: 0.25em;
161
+ align-items: center;
162
+ background-color: var(--button-bg-color);
163
+ }
164
+ div.sensor-things-location-select-list {
165
+ padding: 0.25em;
166
+ max-height: 16em;
167
+ overflow-y: auto;
168
+ }
169
+ div.sensor-things-location-select-list div {
162
170
  padding-top: 0.25em;
163
171
  }
164
- div.sensor-things-location-select div:hover {
172
+ div.sensor-things-location-select-list div:hover {
165
173
  font-weight: bold;
166
174
  cursor: pointer;
167
175
  }
@@ -150,7 +150,7 @@ export default function layers() {
150
150
  }
151
151
  case ADD_LAYER:
152
152
  {
153
- var _action$layer$srcid, _action$layer$visibil, _action$layer$opacity, _action$options, _action$options2;
153
+ var _action$layer$srcid, _action$layer$visibil, _action$layer$opacity, _ref, _action$options$layer, _action$options, _action$options2, _action$options3;
154
154
  var _newLayers2 = (state.flat || []).concat();
155
155
  var layerId = action.layer.id || uuidv4();
156
156
  var newLayer = _objectSpread(_objectSpread({}, action.layer), {}, {
@@ -161,9 +161,9 @@ export default function layers() {
161
161
  queryable: action.layer.queryable || false,
162
162
  visibility: (_action$layer$visibil = action.layer.visibility) !== null && _action$layer$visibil !== void 0 ? _action$layer$visibil : true,
163
163
  opacity: (_action$layer$opacity = action.layer.opacity) !== null && _action$layer$opacity !== void 0 ? _action$layer$opacity : 255,
164
- layertreehidden: action.layer.layertreehidden || action.layer.role > LayerRole.USERLAYER
164
+ layertreehidden: (_ref = (_action$options$layer = (_action$options = action.options) === null || _action$options === void 0 ? void 0 : _action$options.layertreehidden) !== null && _action$options$layer !== void 0 ? _action$options$layer : action.layer.layertreehidden) !== null && _ref !== void 0 ? _ref : action.layer.role > LayerRole.USERLAYER
165
165
  });
166
- if ((_action$options = action.options) !== null && _action$options !== void 0 && _action$options.beforeLayerName || (_action$options2 = action.options) !== null && _action$options2 !== void 0 && _action$options2.afterLayerName) {
166
+ if ((_action$options2 = action.options) !== null && _action$options2 !== void 0 && _action$options2.beforeLayerName || (_action$options3 = action.options) !== null && _action$options3 !== void 0 && _action$options3.afterLayerName) {
167
167
  _newLayers2 = LayerUtils.insertLayer(_newLayers2, newLayer, "name", action.options.beforeLayerName || action.options.afterLayerName, action.options.afterLayerName ? true : false);
168
168
  } else {
169
169
  var inspos = 0;
@@ -315,6 +315,7 @@ export default function layers() {
315
315
  return layer.id === _layerId;
316
316
  });
317
317
  if (idx === -1) {
318
+ var _action$layer$srcid2;
318
319
  var newFeatures = action.features.map(function (f) {
319
320
  return _objectSpread(_objectSpread({}, f), {}, {
320
321
  id: f.id || (f.properties || {}).id || uuidv4()
@@ -330,7 +331,8 @@ export default function layers() {
330
331
  visibility: action.layer.visibility || true,
331
332
  opacity: action.layer.opacity || 255,
332
333
  layertreehidden: action.layer.layertreehidden || action.layer.role > LayerRole.USERLAYER,
333
- bbox: VectorLayerUtils.computeFeaturesBBox(action.features)
334
+ bbox: VectorLayerUtils.computeFeaturesBBox(action.features),
335
+ srcid: (_action$layer$srcid2 = action.layer.srcid) !== null && _action$layer$srcid2 !== void 0 ? _action$layer$srcid2 : uuidv4()
334
336
  });
335
337
  var _inspos = 0;
336
338
  for (; _inspos < _newLayers6.length && _newLayer2.role < _newLayers6[_inspos].role; ++_inspos);
package/reducers/task.js CHANGED
@@ -19,7 +19,8 @@ var defaultState = {
19
19
  data: null,
20
20
  blocked: false,
21
21
  unsetOnMapClick: false,
22
- identifyEnabled: true
22
+ identifyEnabled: true,
23
+ unblockedIdentifyEnabled: true
23
24
  };
24
25
  export default function task() {
25
26
  var state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : defaultState;
@@ -35,13 +36,15 @@ export default function task() {
35
36
  mode: action.mode,
36
37
  data: action.data,
37
38
  unsetOnMapClick: action.unsetOnMapClick,
38
- identifyEnabled: action.identifyEnabled
39
+ identifyEnabled: action.identifyEnabled,
40
+ unblockedIdentifyEnabled: action.identifyEnabled
39
41
  });
40
42
  }
41
43
  case SET_CURRENT_TASK_BLOCKED:
42
44
  {
43
45
  return _objectSpread(_objectSpread({}, state), {}, {
44
- blocked: action.blocked
46
+ blocked: action.blocked,
47
+ identifyEnabled: action.blocked ? false : state.unblockedIdentifyEnabled
45
48
  });
46
49
  }
47
50
  default:
@@ -43,6 +43,7 @@
43
43
  "FeatureSearch": "Търсене на функции",
44
44
  "GeometryDigitizer": "Дигитайзер на геометрия",
45
45
  "IdentifyPoint": "Идентифициране на точка",
46
+ "IdentifyRadius": "",
46
47
  "IdentifyRegion": "Определяне на региона",
47
48
  "LayerCatalog": "Каталог на слоевете",
48
49
  "Login": "Вход",
@@ -43,6 +43,7 @@
43
43
  "FeatureSearch": "Cerca d'element",
44
44
  "GeometryDigitizer": "Digitalitzador de geometria",
45
45
  "IdentifyPoint": "Identificar punt",
46
+ "IdentifyRadius": "",
46
47
  "IdentifyRegion": "Identificar zona",
47
48
  "LayerCatalog": "Capes",
48
49
  "Login": "Inicio de sessió",
@@ -43,6 +43,7 @@
43
43
  "FeatureSearch": "",
44
44
  "GeometryDigitizer": "",
45
45
  "IdentifyPoint": "Informace o bodu",
46
+ "IdentifyRadius": "",
46
47
  "IdentifyRegion": "Informace o oblasti",
47
48
  "LayerCatalog": "Katalog vrstev",
48
49
  "Login": "Přihlášení",
@@ -43,6 +43,7 @@
43
43
  "FeatureSearch": "Objektsuche",
44
44
  "GeometryDigitizer": "Geometriedigitalisierung",
45
45
  "IdentifyPoint": "Punkt abfragen",
46
+ "IdentifyRadius": "Radius abfragen",
46
47
  "IdentifyRegion": "Region abfragen",
47
48
  "LayerCatalog": "Ebenenkatalog",
48
49
  "Login": "Anmelden",
@@ -43,6 +43,7 @@
43
43
  "FeatureSearch": "Objektsuche",
44
44
  "GeometryDigitizer": "Geometriedigitalisierung",
45
45
  "IdentifyPoint": "Punkt abfragen",
46
+ "IdentifyRadius": "Radius abfragen",
46
47
  "IdentifyRegion": "Region abfragen",
47
48
  "LayerCatalog": "Ebenenkatalog",
48
49
  "Login": "Anmelden",
@@ -43,6 +43,7 @@
43
43
  "FeatureSearch": "Feature Search",
44
44
  "GeometryDigitizer": "Geometry digitizer",
45
45
  "IdentifyPoint": "Identify Point",
46
+ "IdentifyRadius": "Identify in Radius",
46
47
  "IdentifyRegion": "Identify Region",
47
48
  "LayerCatalog": "Layer catalog",
48
49
  "Login": "Login",
@@ -43,6 +43,7 @@
43
43
  "FeatureSearch": "Búsqueda de elemento",
44
44
  "GeometryDigitizer": "Digitalizador de geometría",
45
45
  "IdentifyPoint": "Identificar punto",
46
+ "IdentifyRadius": "",
46
47
  "IdentifyRegion": "Identificar zona",
47
48
  "LayerCatalog": "Capa catálogo",
48
49
  "Login": "Inicio de sesión",
@@ -43,6 +43,7 @@
43
43
  "FeatureSearch": "",
44
44
  "GeometryDigitizer": "",
45
45
  "IdentifyPoint": "",
46
+ "IdentifyRadius": "",
46
47
  "IdentifyRegion": "Tunnista alue",
47
48
  "LayerCatalog": "",
48
49
  "Login": "Kirjaudu",
@@ -43,6 +43,7 @@
43
43
  "FeatureSearch": "Recherche objet",
44
44
  "GeometryDigitizer": "Digitalisation des géométries",
45
45
  "IdentifyPoint": "Interroger point",
46
+ "IdentifyRadius": "Interroger dans un rayon",
46
47
  "IdentifyRegion": "Interroger la région",
47
48
  "LayerCatalog": "Catalogue des couches",
48
49
  "Login": "Connexion",
@@ -43,6 +43,7 @@
43
43
  "FeatureSearch": "",
44
44
  "GeometryDigitizer": "",
45
45
  "IdentifyPoint": "",
46
+ "IdentifyRadius": "",
46
47
  "IdentifyRegion": "Adatok lekérdezése",
47
48
  "LayerCatalog": "",
48
49
  "Login": "",
@@ -43,6 +43,7 @@
43
43
  "FeatureSearch": "Ricerca oggetto",
44
44
  "GeometryDigitizer": "Digitalizzazione geometrie",
45
45
  "IdentifyPoint": "Identifica punto",
46
+ "IdentifyRadius": "Identifica in un raggio",
46
47
  "IdentifyRegion": "Identifica in un poligono",
47
48
  "LayerCatalog": "Catalogo livelli",
48
49
  "Login": "Accedi",
@@ -43,6 +43,7 @@
43
43
  "FeatureSearch": "地物検索",
44
44
  "GeometryDigitizer": "ジオメトリ・デジタイザ",
45
45
  "IdentifyPoint": "点で地物を確認",
46
+ "IdentifyRadius": "",
46
47
  "IdentifyRegion": "領域内の地物を確認",
47
48
  "LayerCatalog": "レイヤ・カタログ",
48
49
  "Login": "ログイン",
@@ -43,6 +43,7 @@
43
43
  "FeatureSearch": "Zoeken op kenmerken",
44
44
  "GeometryDigitizer": "Geometry digitizer",
45
45
  "IdentifyPoint": "",
46
+ "IdentifyRadius": "",
46
47
  "IdentifyRegion": "Data opvragen",
47
48
  "LayerCatalog": "Lagencatalogus",
48
49
  "Login": "Inloggen",
@@ -43,6 +43,7 @@
43
43
  "FeatureSearch": "",
44
44
  "GeometryDigitizer": "",
45
45
  "IdentifyPoint": "",
46
+ "IdentifyRadius": "",
46
47
  "IdentifyRegion": "Identifiser med område",
47
48
  "LayerCatalog": "",
48
49
  "Login": "Logg inn",
@@ -43,6 +43,7 @@
43
43
  "FeatureSearch": "",
44
44
  "GeometryDigitizer": "",
45
45
  "IdentifyPoint": "",
46
+ "IdentifyRadius": "",
46
47
  "IdentifyRegion": "Identifikuj Obszar",
47
48
  "LayerCatalog": "",
48
49
  "Login": "Zaloguj",
@@ -43,6 +43,7 @@
43
43
  "FeatureSearch": "Procurar feição",
44
44
  "GeometryDigitizer": "",
45
45
  "IdentifyPoint": "Identificar ponto",
46
+ "IdentifyRadius": "",
46
47
  "IdentifyRegion": "Identificar região",
47
48
  "LayerCatalog": "Catálogo de camadas",
48
49
  "Login": "Inicio da sessão",
@@ -43,6 +43,7 @@
43
43
  "FeatureSearch": "Pesquisa de Recurso",
44
44
  "GeometryDigitizer": "",
45
45
  "IdentifyPoint": "Identificar Ponto",
46
+ "IdentifyRadius": "",
46
47
  "IdentifyRegion": "Identificar Região",
47
48
  "LayerCatalog": "Catálogo de Camadas",
48
49
  "Login": "Iniciar Sessão",
@@ -43,6 +43,7 @@
43
43
  "FeatureSearch": "",
44
44
  "GeometryDigitizer": "",
45
45
  "IdentifyPoint": "Interogare punct",
46
+ "IdentifyRadius": "",
46
47
  "IdentifyRegion": "Interogare regiune",
47
48
  "LayerCatalog": "Catalog de straturi",
48
49
  "Login": "Conectează-te",
@@ -43,6 +43,7 @@
43
43
  "FeatureSearch": "",
44
44
  "GeometryDigitizer": "",
45
45
  "IdentifyPoint": "",
46
+ "IdentifyRadius": "",
46
47
  "IdentifyRegion": "Определить регион",
47
48
  "LayerCatalog": "",
48
49
  "Login": "",
@@ -43,6 +43,7 @@
43
43
  "FeatureSearch": "",
44
44
  "GeometryDigitizer": "",
45
45
  "IdentifyPoint": "",
46
+ "IdentifyRadius": "",
46
47
  "IdentifyRegion": "Identifiera med område",
47
48
  "LayerCatalog": "",
48
49
  "Login": "Logga in",
@@ -43,6 +43,7 @@
43
43
  "FeatureSearch": "Obje Arama",
44
44
  "GeometryDigitizer": "Geometri Sayısallaştırıcı",
45
45
  "IdentifyPoint": "Nokta bilgisi al",
46
+ "IdentifyRadius": "",
46
47
  "IdentifyRegion": "Bölgeden Bilgi Al",
47
48
  "LayerCatalog": "Katman Kataloğu",
48
49
  "Login": "Giriş",
@@ -34,6 +34,7 @@
34
34
  "appmenu.items.GeometryDigitizer",
35
35
  "appmenu.items.Help",
36
36
  "appmenu.items.IdentifyPoint",
37
+ "appmenu.items.IdentifyRadius",
37
38
  "appmenu.items.IdentifyRegion",
38
39
  "appmenu.items.LayerCatalog",
39
40
  "appmenu.items.LayerTree",
@@ -43,6 +43,7 @@
43
43
  "FeatureSearch": "",
44
44
  "GeometryDigitizer": "",
45
45
  "IdentifyPoint": "",
46
+ "IdentifyRadius": "",
46
47
  "IdentifyRegion": "Визначити регіон",
47
48
  "LayerCatalog": "",
48
49
  "Login": "",
@@ -374,7 +374,7 @@ var EditingInterface = {
374
374
  * @param editConfig The edit config of the feature dataset
375
375
  * @param featureId The feature ID
376
376
  * @param mapCrs The CRS of the map, as an EPSG code
377
- * @param tables Comma separated string of relation table names
377
+ * @param tables Comma separated string of relation table references in the form `<table_name>:<fk_name>:<sort_col>`
378
378
  * @param editConfigs The theme editConfig block, containing all theme dataset edit configs
379
379
  * @param callback Callback invoked with the relation values, taking `{<tablename>: {<relation_values>}}` on success and `{}` on failure
380
380
  */
@@ -163,7 +163,8 @@ var LayerUtils = {
163
163
  type: "separator",
164
164
  title: title,
165
165
  role: LayerRole.USERLAYER,
166
- id: uuidv4()
166
+ id: uuidv4(),
167
+ srcid: uuidv4()
167
168
  }]);
168
169
  },
169
170
  createExternalLayerPlaceholder: function createExternalLayerPlaceholder(layerConfig, externalLayers, id) {