@iobroker/gui-components 10.3.5 → 10.3.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.
package/README.md CHANGED
@@ -36,7 +36,7 @@ npm create vite@latest src -- --template react-ts
36
36
  "dependencies": {
37
37
  "@emotion/react": "^11.14.0",
38
38
  "@emotion/styled": "^11.14.1",
39
- "@iobroker/gui-components": "^10.3.5",
39
+ "@iobroker/gui-components": "^10.3.6",
40
40
  "@mui/icons-material": "^9.0.1",
41
41
  "@mui/material": "^9.0.1",
42
42
  "react": "^19.2.5",
@@ -877,6 +877,12 @@ You can find the migration instructions:
877
877
  -->
878
878
 
879
879
  ## Changelog
880
+ ### 10.3.6 (2026-09-26)
881
+
882
+ - (@GermanBluefox) Fixed: `Router.doNavigate` wrote the ID into the URL hash as it was, while `Router.getLocation` reads the hash by splitting it at "/" and decoding every part. An ID that contains a "/" - e.g. `ocpp.0./TACW1142021G1543.1.meterValues.Power_Active_Import` - was therefore cut off at its first slash on the way back, and its remainder landed in `arg`. The ID segment is URL-encoded now, so it survives the round trip, including the one a dialog makes when it only changes its tab (ioBroker.admin#3634). **Callers must pass the raw ID**: one that is already encoded is now encoded twice
883
+ - (@GermanBluefox) Changed: without the expert mode, the object browser no longer offers to delete an object of an adapter - neither the button in the row nor the entry in the context menu nor the `Delete` key. Deleting such an object can stop the adapter from working, and it is created again on the next start anyway. What a user creates themselves (`0_userdata.*`, `alias.*`) can still be deleted without the expert mode, and with the expert mode everything can be deleted as before (ioBroker.admin#3639)
884
+ - (@GermanBluefox) Fixed: the object browser threw `can't access property "<id>", this.objects is undefined` when a navigation target reached it before the object tree was read - which is what a route in the URL does. Such a target now waits for the tree instead of crashing the browser (ioBroker.admin#3634)
885
+
880
886
  ### 10.3.5 (2026-09-25)
881
887
 
882
888
  - (@GermanBluefox) Added function `findObjectIcon`
@@ -218,6 +218,16 @@ export declare class ObjectBrowserClass extends Component<ObjectBrowserProps, Ob
218
218
  * Called when component is unmounted.
219
219
  */
220
220
  componentWillUnmount(): void;
221
+ /**
222
+ * May this object be deleted without the expert mode?
223
+ *
224
+ * Deleting an object of an adapter can stop that adapter from working, and the adapter creates it
225
+ * again on the next start anyway. So outside the expert mode only the objects a user makes
226
+ * themselves can be deleted - those in `0_userdata.*` and `alias.*` (ioBroker.admin#3639).
227
+ *
228
+ * @param id ID of the object
229
+ */
230
+ isDeleteAllowed(id: string): boolean;
221
231
  /**
222
232
  * Show the deletion dialog for a given object
223
233
  */
@@ -27,6 +27,11 @@ import * as contextMenu from './contextMenu';
27
27
  import { DEFAULT_DATE_FORMAT, DEFAULT_FILTER, ITEM_LEVEL, MIN_COLUMN_WIDTHS, SCREEN_WIDTHS, } from './constants';
28
28
  export { getSelectIdIconFromObjects };
29
29
  let objectsAlreadyLoaded = false;
30
+ /**
31
+ * The namespaces whose objects a user creates themselves. Only these may be deleted without the
32
+ * expert mode; see {@link ObjectBrowserClass.isDeleteAllowed}.
33
+ */
34
+ const USER_OWNED_NAMESPACES = ['0_userdata.', 'alias.'];
30
35
  export class ObjectBrowserClass extends Component {
31
36
  // do not define the type as null to save the performance, so we must check it every time
32
37
  info = {
@@ -787,11 +792,26 @@ export class ObjectBrowserClass extends Component {
787
792
  this.subscribes.clear();
788
793
  this.objects = {};
789
794
  }
795
+ /**
796
+ * May this object be deleted without the expert mode?
797
+ *
798
+ * Deleting an object of an adapter can stop that adapter from working, and the adapter creates it
799
+ * again on the next start anyway. So outside the expert mode only the objects a user makes
800
+ * themselves can be deleted - those in `0_userdata.*` and `alias.*` (ioBroker.admin#3639).
801
+ *
802
+ * @param id ID of the object
803
+ */
804
+ isDeleteAllowed(id) {
805
+ return this.state.filter.expertMode || USER_OWNED_NAMESPACES.some(prefix => id.startsWith(prefix));
806
+ }
790
807
  /**
791
808
  * Show the deletion dialog for a given object
792
809
  */
793
810
  showDeleteDialog(options) {
794
811
  const { id, obj, item } = options;
812
+ if (!this.isDeleteAllowed(id)) {
813
+ return;
814
+ }
795
815
  // calculate the number of children
796
816
  const keys = Object.keys(this.objects);
797
817
  keys.sort();
@@ -864,9 +884,12 @@ export class ObjectBrowserClass extends Component {
864
884
  */
865
885
  onSelect(toggleItem, isDouble, cb) {
866
886
  this.localStorage.setItem(`${this.props.dialogName || 'App'}.focused`, toggleItem);
887
+ // `objects` only exists once the tree has been read. A selection can arrive before that -
888
+ // from the URL, through `navigateTo` - and must not throw; such an ID is treated like one
889
+ // that is not in the tree, and `applyInitialNavigateTo` selects it properly after the load.
890
+ const obj = this.objects?.[toggleItem];
867
891
  if (!this.props.multiSelect) {
868
- if (this.objects[toggleItem] &&
869
- (!this.props.types || this.props.types.includes(this.objects[toggleItem].type))) {
892
+ if (obj && (!this.props.types || this.props.types.includes(obj.type))) {
870
893
  this.localStorage.removeItem(`${this.props.dialogName || 'App'}.selectedNonObject`);
871
894
  if (this.state.selected[0] !== toggleItem) {
872
895
  this.setState({ selected: [toggleItem], selectedNonObject: '', focused: toggleItem }, () => {
@@ -890,8 +913,7 @@ export class ObjectBrowserClass extends Component {
890
913
  });
891
914
  }
892
915
  }
893
- else if (this.objects[toggleItem] &&
894
- (!this.props.types || this.props.types.includes(this.objects[toggleItem].type))) {
916
+ else if (obj && (!this.props.types || this.props.types.includes(obj.type))) {
895
917
  this.localStorage.removeItem(`${this.props.dialogName || 'App'}.selectedNonObject`);
896
918
  const selected = [...this.state.selected];
897
919
  const pos = selected.indexOf(toggleItem);
@@ -2493,6 +2515,11 @@ export class ObjectBrowserClass extends Component {
2493
2515
  }
2494
2516
  /** Apply a navigation target coming from the parent (`navigateTo`): select + open the dialog. */
2495
2517
  applyNavigateTo(nav) {
2518
+ if (!this.objects) {
2519
+ // The tree is still being read. `applyInitialNavigateTo` runs once it is there and takes
2520
+ // the target from the props again, so nothing is lost by waiting here.
2521
+ return;
2522
+ }
2496
2523
  this.applyingNav = true;
2497
2524
  const done = () => {
2498
2525
  this.applyingNav = false;