qwc2 2026.5.11 → 2026.5.25

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 (44) hide show
  1. package/actions/theme.js +1 -14
  2. package/components/AttributeForm.js +8 -0
  3. package/components/MapButton.js +0 -12
  4. package/components/PluginsContainer.js +24 -20
  5. package/components/QtDesignerForm.js +2 -1
  6. package/components/ResizeableWindow.js +4 -2
  7. package/components/widgets/DateTimeInput.js +57 -16
  8. package/package.json +1 -1
  9. package/plugins/BackgroundSwitcher.js +157 -71
  10. package/plugins/Portal.js +12 -1
  11. package/plugins/Print.js +3 -3
  12. package/plugins/Redlining.js +1 -1
  13. package/plugins/map/RedliningSupport.js +2 -2
  14. package/plugins/map3d/BackgroundSwitcher3D.js +20 -3
  15. package/plugins/style/BackgroundSwitcher.css +41 -10
  16. package/plugins/style/Portal.css +7 -0
  17. package/reducers/theme.js +2 -9
  18. package/static/translations/bg-BG.json +1 -0
  19. package/static/translations/ca-ES.json +1 -0
  20. package/static/translations/cs-CZ.json +1 -0
  21. package/static/translations/de-CH.json +1 -0
  22. package/static/translations/de-DE.json +1 -0
  23. package/static/translations/en-US.json +1 -0
  24. package/static/translations/es-ES.json +1 -0
  25. package/static/translations/fi-FI.json +1 -0
  26. package/static/translations/fr-FR.json +1 -0
  27. package/static/translations/hu-HU.json +1 -0
  28. package/static/translations/it-IT.json +1 -0
  29. package/static/translations/ja-JP.json +1 -0
  30. package/static/translations/nl-NL.json +1 -0
  31. package/static/translations/no-NO.json +1 -0
  32. package/static/translations/pl-PL.json +1 -0
  33. package/static/translations/pt-BR.json +1 -0
  34. package/static/translations/pt-PT.json +1 -0
  35. package/static/translations/ro-RO.json +1 -0
  36. package/static/translations/ru-RU.json +1 -0
  37. package/static/translations/sv-SE.json +1 -0
  38. package/static/translations/tr-TR.json +1 -0
  39. package/static/translations/tsconfig.json +1 -0
  40. package/static/translations/uk-UA.json +1 -0
  41. package/utils/CoordinatesUtils.js +3 -3
  42. package/utils/IdentifyUtils.js +1 -1
  43. package/utils/LayerUtils.js +24 -13
  44. package/utils/ThemeUtils.js +5 -2
package/actions/theme.js CHANGED
@@ -39,7 +39,6 @@ import { showNotification, NotificationType } from './windows';
39
39
  export var THEMES_LOADED = 'THEMES_LOADED';
40
40
  export var SET_THEME_LAYERS_LIST = 'SET_THEME_LAYERS_LIST';
41
41
  export var SET_CURRENT_THEME = 'SET_CURRENT_THEME';
42
- export var SWITCHING_THEME = 'SWITCHING_THEME';
43
42
  export function themesLoaded(themes) {
44
43
  return {
45
44
  type: THEMES_LOADED,
@@ -89,7 +88,7 @@ export function finishThemeSetup(dispatch, theme, themes, layerConfigs, insertPo
89
88
  dispatch(showNotification("missingbglayer", LocaleUtils.tr("app.missingbg", visibleBgLayer), NotificationType.WARN, true));
90
89
  }
91
90
  }
92
- var _iterator = _createForOfIteratorHelper(bgLayers),
91
+ var _iterator = _createForOfIteratorHelper(bgLayers.reverse()),
93
92
  _step;
94
93
  try {
95
94
  for (_iterator.s(); !(_step = _iterator.n()).done;) {
@@ -144,10 +143,6 @@ export function finishThemeSetup(dispatch, theme, themes, layerConfigs, insertPo
144
143
  type: SET_CURRENT_THEME,
145
144
  theme: theme
146
145
  });
147
- dispatch({
148
- type: SWITCHING_THEME,
149
- switching: false
150
- });
151
146
  var section = ConfigUtils.isMobile() ? "mobile" : "desktop";
152
147
  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);
153
148
  if (task) {
@@ -173,10 +168,6 @@ export function setCurrentTheme(theme, themes) {
173
168
  return;
174
169
  }
175
170
  var initialTheme = !getState().theme.current;
176
- dispatch({
177
- type: SWITCHING_THEME,
178
- switching: true
179
- });
180
171
 
181
172
  // Get current background layer if it needs to be preserved
182
173
  if (preserve && visibleBgLayer === null && ConfigUtils.getConfigProp("preserveBackgroundOnThemeSwitch", theme) === true) {
@@ -215,10 +206,6 @@ export function setCurrentTheme(theme, themes) {
215
206
  }
216
207
  dispatch(setSwipe(null));
217
208
  if (!theme) {
218
- dispatch({
219
- type: SWITCHING_THEME,
220
- switching: false
221
- });
222
209
  return;
223
210
  }
224
211
 
@@ -597,6 +597,10 @@ var AttributeForm = /*#__PURE__*/function (_React$Component) {
597
597
  // Convert text NULL to null
598
598
  value = null;
599
599
  }
600
+ if (nrelFieldConfig.type === 'number' && value !== null) {
601
+ // Ensure numeric values are numbers and not strings
602
+ value = Number(value);
603
+ }
600
604
 
601
605
  // relationValues for table must exist as rows are either pre-existing or were added
602
606
  if (!(field in relationValues[datasetname].features[index].properties)) {
@@ -639,6 +643,10 @@ var AttributeForm = /*#__PURE__*/function (_React$Component) {
639
643
  // Convert text NULL to null
640
644
  value = null;
641
645
  }
646
+ if (fieldConfig.type === 'number' && value !== null) {
647
+ // Ensure numeric values are numbers and not strings
648
+ value = Number(value);
649
+ }
642
650
  if (!(name in feature.properties)) {
643
651
  feature.defaultedProperties = [].concat(_toConsumableArray(feature.defaultedProperties || []), [name]);
644
652
  }
@@ -35,18 +35,6 @@ var MapButton = /*#__PURE__*/function (_React$Component) {
35
35
  }
36
36
  _inherits(MapButton, _React$Component);
37
37
  return _createClass(MapButton, [{
38
- key: "componentDidMount",
39
- value: function componentDidMount() {
40
- this.componentDidUpdate({});
41
- }
42
- }, {
43
- key: "componentDidUpdate",
44
- value: function componentDidUpdate(prevProps) {
45
- if (this.context && this.props.position !== prevProps.position) {
46
- this.context.recomputeSpacers();
47
- }
48
- }
49
- }, {
50
38
  key: "render",
51
39
  value: function render() {
52
40
  if (!this.context) {
@@ -171,6 +171,7 @@ var PluginsContainer = /*#__PURE__*/function (_React$Component) {
171
171
  // Reorder child nodes for natural tab focus order
172
172
  var children = _toConsumableArray(el.children);
173
173
  var slots = {};
174
+ var usedSlots = new Set();
174
175
  children.forEach(function (child) {
175
176
  if (child.dataset.subslot === undefined) {
176
177
  var _slots$child$dataset$;
@@ -178,12 +179,35 @@ var PluginsContainer = /*#__PURE__*/function (_React$Component) {
178
179
  slots[child.dataset.slot] = child.dataset.subslot = subslot;
179
180
  child.style.order = 100 * parseInt(child.style.order, 10) + subslot;
180
181
  }
182
+ if (child.dataset.slot !== undefined) {
183
+ usedSlots.add(child.dataset.slot);
184
+ }
181
185
  });
182
186
  children.sort(function (a, b) {
183
187
  return b.style.order - a.style.order;
184
188
  }).forEach(function (node) {
185
189
  return el.appendChild(node);
186
190
  });
191
+ var slotString = _toConsumableArray(usedSlots).sort().join(":");
192
+ if (slotString !== el.dataset.slotString) {
193
+ // Recompute spacers
194
+ children.forEach(function (child) {
195
+ if (child.dataset.spacer) {
196
+ el.removeChild(child);
197
+ }
198
+ });
199
+ var maxSlot = Math.max.apply(Math, _toConsumableArray(usedSlots));
200
+ for (var i = 0; i < maxSlot; ++i) {
201
+ if (!usedSlots.has(String(i))) {
202
+ var child = document.createElement("div");
203
+ child.className = "map-buttons-spacer";
204
+ child.dataset.spacer = 1;
205
+ child.style.order = 100 * i;
206
+ el.appendChild(child);
207
+ }
208
+ }
209
+ el.dataset.slotString = slotString;
210
+ }
187
211
  }
188
212
  };
189
213
  for (_iterator.s(); !(_step = _iterator.n()).done;) {
@@ -202,26 +226,6 @@ var PluginsContainer = /*#__PURE__*/function (_React$Component) {
202
226
  childList: true
203
227
  });
204
228
  resizeObserver.observe(el);
205
- el.recomputeSpacers = function () {
206
- var slots = new Set();
207
- Array.from(el.childNodes).forEach(function (child) {
208
- if (child.dataset.spacer) {
209
- el.removeChild(child);
210
- } else {
211
- slots.add(child.dataset.slot);
212
- }
213
- });
214
- var maxSlot = Math.max.apply(Math, _toConsumableArray(slots));
215
- for (var i = 0; i < maxSlot; ++i) {
216
- if (!slots.has(String(i))) {
217
- var child = document.createElement("div");
218
- child.className = "map-buttons-spacer";
219
- child.dataset.spacer = 1;
220
- child.style.order = 100 * i;
221
- el.appendChild(child);
222
- }
223
- }
224
- };
225
229
  }
226
230
  });
227
231
  return _this;
@@ -612,7 +612,8 @@ var QtDesignerForm = /*#__PURE__*/function (_React$Component) {
612
612
  readOnly: inputConstraints.readOnly,
613
613
  required: inputConstraints.required,
614
614
  style: fontStyle,
615
- value: value
615
+ value: value,
616
+ valueHasTz: field.data_type === "timestamp with time zone"
616
617
  });
617
618
  } else if (widget["class"] === "QListWidget") {
618
619
  return /*#__PURE__*/React.createElement(ListInput, {
@@ -54,7 +54,7 @@ var ResizeableWindow = /*#__PURE__*/function (_React$Component) {
54
54
  ev.stopPropagation();
55
55
  });
56
56
  _defineProperty(_this, "renderTitleBar", function () {
57
- if (_this.props.fullscreen) {
57
+ if (_this.props.fullscreen || _this.props.hideTitleBar) {
58
58
  return null;
59
59
  }
60
60
  var maximized = _this.state.geometry.maximized ? true : false;
@@ -118,7 +118,7 @@ var ResizeableWindow = /*#__PURE__*/function (_React$Component) {
118
118
  onClick: entry.callback,
119
119
  title: entry.title
120
120
  });
121
- }), !maximized && dockable ? /*#__PURE__*/React.createElement(Icon, {
121
+ }), !maximized && dockable && !_this.props.hideDockIcon ? /*#__PURE__*/React.createElement(Icon, {
122
122
  className: iconClasses,
123
123
  icon: dockIcon,
124
124
  onClick: _this.toggleDock,
@@ -549,6 +549,8 @@ _defineProperty(ResizeableWindow, "propTypes", {
549
549
  })),
550
550
  fitHeight: PropTypes.bool,
551
551
  fullscreen: PropTypes.bool,
552
+ hideDockIcon: PropTypes.bool,
553
+ hideTitleBar: PropTypes.bool,
552
554
  icon: PropTypes.string,
553
555
  initialHeight: PropTypes.number,
554
556
  initialWidth: PropTypes.number,
@@ -1,6 +1,12 @@
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
2
  function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
3
3
  function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
4
+ function _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); }
5
+ 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."); }
6
+ 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; } }
7
+ function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
8
+ function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
9
+ function _arrayWithHoles(r) { if (Array.isArray(r)) return r; }
4
10
  function _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); }
5
11
  function _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } }
6
12
  function _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", { writable: !1 }), e; }
@@ -34,14 +40,42 @@ var DateTimeInput = /*#__PURE__*/function (_React$Component) {
34
40
  args[_key] = arguments[_key];
35
41
  }
36
42
  _this = _callSuper(this, DateTimeInput, [].concat(args));
37
- _defineProperty(_this, "valueChanged", function (date, time) {
38
- if (time && time.length === 5) {
39
- time += ":00";
43
+ _defineProperty(_this, "valueChanged", function (datestr, timestr) {
44
+ var date = new Date();
45
+ if (datestr) {
46
+ var _datestr$split$map = datestr.split("-").map(Number),
47
+ _datestr$split$map2 = _slicedToArray(_datestr$split$map, 3),
48
+ year = _datestr$split$map2[0],
49
+ month = _datestr$split$map2[1],
50
+ day = _datestr$split$map2[2];
51
+ date.setFullYear(year);
52
+ date.setMonth(month - 1);
53
+ date.setDate(day);
40
54
  }
41
- if (date && time) {
42
- _this.props.onChange(date + "T" + time);
43
- } else if (date) {
44
- _this.props.onChange(date);
55
+ if (timestr) {
56
+ timestr = timestr + ":00:00";
57
+ var _timestr$split$slice$ = timestr.split(":").slice(0, 3).map(Number),
58
+ _timestr$split$slice$2 = _slicedToArray(_timestr$split$slice$, 3),
59
+ hours = _timestr$split$slice$2[0],
60
+ minutes = _timestr$split$slice$2[1],
61
+ seconds = _timestr$split$slice$2[2];
62
+ date.setHours(hours);
63
+ date.setMinutes(minutes);
64
+ date.setSeconds(seconds);
65
+ } else {
66
+ date.setHours(0);
67
+ date.setMinutes(0);
68
+ date.setSeconds(0);
69
+ }
70
+ if (datestr || timestr) {
71
+ if (_this.props.valueHasTz) {
72
+ _this.props.onChange(date.toISOString());
73
+ } else {
74
+ var pad = function pad(n) {
75
+ return String(n).padStart(2, "0");
76
+ };
77
+ _this.props.onChange(date.getFullYear() + "-" + pad(date.getMonth() + 1) + "-" + pad(date.getDate()) + "T" + pad(date.getHours()) + ":" + pad(date.getMinutes()) + ":" + pad(date.getSeconds()));
78
+ }
45
79
  } else {
46
80
  _this.props.onChange("");
47
81
  }
@@ -53,27 +87,33 @@ var DateTimeInput = /*#__PURE__*/function (_React$Component) {
53
87
  key: "render",
54
88
  value: function render() {
55
89
  var _this2 = this;
56
- var parts = (this.props.value || "T").split("T");
57
- parts[1] = (parts[1] || "").replace(/\+[0-9:]+$/, ''); // Strip timezone
58
- parts[1] = (parts[1] || "").replace(/\.\d+$/, ''); // Strip milliseconds
90
+ var date = new Date(this.props.valueHasTz ? this.props.value : this.props.value.slice(0, 19));
91
+ var datestr = "";
92
+ var timestr = "";
93
+ if (!Number.isNaN(date.getTime())) {
94
+ var pad = function pad(n) {
95
+ return String(n).padStart(2, "0");
96
+ };
97
+ datestr = "".concat(date.getFullYear(), "-").concat(pad(date.getMonth() + 1), "-").concat(pad(date.getDate()));
98
+ timestr = "".concat(pad(date.getHours()), ":").concat(pad(date.getMinutes()), ":").concat(pad(date.getSeconds()));
99
+ }
59
100
  return /*#__PURE__*/React.createElement(InputContainer, {
60
101
  className: "DateTimeInput"
61
102
  }, /*#__PURE__*/React.createElement("input", {
62
103
  max: this.props.maxDate,
63
104
  min: this.props.minDate,
64
105
  onChange: function onChange(ev) {
65
- return _this2.valueChanged(ev.target.value, parts[1]);
106
+ return _this2.valueChanged(ev.target.value, timestr);
66
107
  },
67
108
  readOnly: this.props.readOnly,
68
109
  required: this.props.required,
69
110
  role: "input",
70
111
  style: this.props.style,
71
112
  type: "date",
72
- value: parts[0]
113
+ value: datestr
73
114
  }), /*#__PURE__*/React.createElement("input", {
74
- disabled: !parts[0],
75
115
  onChange: function onChange(ev) {
76
- return _this2.valueChanged(parts[0], ev.target.value);
116
+ return _this2.valueChanged(datestr, ev.target.value);
77
117
  },
78
118
  readOnly: this.props.readOnly,
79
119
  required: this.props.required,
@@ -83,7 +123,7 @@ var DateTimeInput = /*#__PURE__*/function (_React$Component) {
83
123
  maxWidth: '8em'
84
124
  }),
85
125
  type: "time",
86
- value: parts[1]
126
+ value: timestr
87
127
  }), /*#__PURE__*/React.createElement("input", {
88
128
  name: this.props.name,
89
129
  role: "input",
@@ -101,6 +141,7 @@ _defineProperty(DateTimeInput, "propTypes", {
101
141
  readOnly: PropTypes.bool,
102
142
  required: PropTypes.bool,
103
143
  style: PropTypes.object,
104
- value: PropTypes.string
144
+ value: PropTypes.string,
145
+ valueHasTz: PropTypes.bool
105
146
  });
106
147
  export { DateTimeInput as default };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qwc2",
3
- "version": "2026.05.11",
3
+ "version": "2026.05.25",
4
4
  "description": "QGIS Web Client",
5
5
  "author": "Sourcepole AG",
6
6
  "license": "BSD-2-Clause",