@iobroker/gui-components 10.2.1 → 10.2.3

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.2.1",
39
+ "@iobroker/gui-components": "^10.2.3",
40
40
  "@mui/icons-material": "^9.0.1",
41
41
  "@mui/material": "^9.0.1",
42
42
  "react": "^19.2.5",
@@ -877,6 +877,19 @@ You can find the migration instructions:
877
877
  -->
878
878
 
879
879
  ## Changelog
880
+ ### 10.2.3 (2026-09-04)
881
+
882
+ - (@GermanBluefox) Fixed: Expert button highlighted in the object browser
883
+
884
+ ### 10.2.2 (2026-09-03)
885
+
886
+ - (@GermanBluefox) Fixed: the object browser lost the column widths as soon as the page was left and opened again. Every width class has its own widths, and the stored ones were written into the class that was guessed before the container was measured, so the table fell back to the defaults of the class it really had: https://github.com/ioBroker/ioBroker.admin/issues/3616
887
+ - (@GermanBluefox) Fixed: after reopening the object browser, the next resize stored only the two columns it touched and dropped every other width the user had set
888
+ - (@GermanBluefox) Fixed: with "Auto" switched off, the checkboxes for "Changed from", "Quality code", "Timestamp" and "Last change" had no effect, and the header of the states view showed those columns although the rows had no cells for them
889
+ - (@GermanBluefox) Fixed: the buttons column could not be resized, as neither it nor the value column in front of it had a resize handle
890
+ - (@GermanBluefox) Fixed: switching "Auto" off did not store the column selection, so after a reload the table showed nothing but the ID column
891
+ - (@GermanBluefox) A double click on a resize handle resets the widths of all width classes now, not only of the current one
892
+
880
893
  ### 10.2.1 (2026-08-31)
881
894
 
882
895
  - (@GermanBluefox) Changed layouts for some components
@@ -32,6 +32,8 @@ export declare class ObjectBrowserClass extends Component<ObjectBrowserProps, Ob
32
32
  private observedContainer;
33
33
  /** Width class for which the columns were calculated the last time */
34
34
  private lastCalculatedWidth;
35
+ /** States view for which the columns were calculated the last time */
36
+ private lastCalculatedStatesView;
35
37
  private pausedSubscribes;
36
38
  /** The tree was rebuilt while subscribes were paused, so the filter must be re-applied on resume */
37
39
  private treeRebuiltWhilePaused;
@@ -74,7 +76,12 @@ export declare class ObjectBrowserClass extends Component<ObjectBrowserProps, Ob
74
76
  private resizerPosition;
75
77
  private resizerActiveDiv;
76
78
  private resizerNextDiv;
77
- private storedWidths;
79
+ /**
80
+ * The column widths the user stored for this dialog, as they were written to
81
+ * `<dialogName>.table`. They are kept independently of the width class, because the class is
82
+ * only known after the container was measured and changes when the window is resized.
83
+ */
84
+ private storedColumnWidths;
78
85
  systemConfig: ioBroker.SystemConfigObject | null;
79
86
  objects: Record<string, ioBroker.Object>;
80
87
  defaultHistory: string;
@@ -128,6 +135,15 @@ export declare class ObjectBrowserClass extends Component<ObjectBrowserProps, Ob
128
135
  initValue: boolean | number | string;
129
136
  };
130
137
  constructor(props: ObjectBrowserProps);
138
+ /**
139
+ * Write the stored column widths into the width class that is active now.
140
+ *
141
+ * Every width class has its own default widths, and the class of the object browser is only
142
+ * known after its container was measured - in the constructor it is still the initial guess.
143
+ * The stored widths must therefore be applied again whenever the class changes, otherwise the
144
+ * table falls back to the defaults of the new class and the user sees their widths reset.
145
+ */
146
+ applyStoredWidths(): void;
131
147
  loadAllObjects(update?: boolean): Promise<void>;
132
148
  expandAllSelected(cb?: () => void): void;
133
149
  /**
@@ -53,6 +53,8 @@ export class ObjectBrowserClass extends Component {
53
53
  observedContainer = null;
54
54
  /** Width class for which the columns were calculated the last time */
55
55
  lastCalculatedWidth = null;
56
+ /** States view for which the columns were calculated the last time */
57
+ lastCalculatedStatesView = null;
56
58
  pausedSubscribes = false;
57
59
  /** The tree was rebuilt while subscribes were paused, so the filter must be re-applied on resume */
58
60
  treeRebuiltWhilePaused = false;
@@ -95,7 +97,12 @@ export class ObjectBrowserClass extends Component {
95
97
  resizerPosition = 0;
96
98
  resizerActiveDiv = null;
97
99
  resizerNextDiv = null;
98
- storedWidths = null;
100
+ /**
101
+ * The column widths the user stored for this dialog, as they were written to
102
+ * `<dialogName>.table`. They are kept independently of the width class, because the class is
103
+ * only known after the container was measured and changes when the window is resized.
104
+ */
105
+ storedColumnWidths = null;
99
106
  systemConfig = null;
100
107
  objects;
101
108
  defaultHistory = '';
@@ -355,29 +362,51 @@ export class ObjectBrowserClass extends Component {
355
362
  const resizerCurrentWidthsStr = this.localStorage.getItem(`${this.props.dialogName || 'App'}.table`);
356
363
  if (resizerCurrentWidthsStr) {
357
364
  try {
358
- const resizerCurrentWidths = JSON.parse(resizerCurrentWidthsStr);
359
- const width = this.width || 'lg';
360
- this.storedWidths = JSON.parse(JSON.stringify(this.screenWidths[width]));
361
- Object.keys(resizerCurrentWidths).forEach(id => {
362
- if (id === 'id') {
363
- this.screenWidths[width].idWidth = resizerCurrentWidths.id;
364
- }
365
- else if (id === 'nameHeader') {
366
- // widths stored by an older version, where the header had an own width
367
- this.screenWidths[width].widths.name = resizerCurrentWidths[id];
368
- }
369
- else if (this.screenWidths[width].widths[id] !== undefined) {
370
- this.screenWidths[width].widths[id] = resizerCurrentWidths[id];
371
- }
365
+ const parsed = JSON.parse(resizerCurrentWidthsStr);
366
+ const storedColumnWidths = {};
367
+ Object.keys(parsed).forEach(id => {
368
+ // widths stored by an older version, where the header had an own width
369
+ storedColumnWidths[id === 'nameHeader' ? 'name' : id] = parsed[id];
372
370
  });
373
- this.customWidth = true;
371
+ this.storedColumnWidths = storedColumnWidths;
372
+ // The resizer has to start from the stored widths too. Without them the next drag
373
+ // would write only the two columns it touched back to the storage, and every other
374
+ // width the user had set would be lost.
375
+ this.resizerCurrentWidths = { ...storedColumnWidths };
374
376
  }
375
377
  catch {
376
- // ignore
378
+ this.storedColumnWidths = null;
377
379
  }
378
380
  }
381
+ this.applyStoredWidths();
379
382
  this.calculateColumnsVisibility();
380
383
  }
384
+ /**
385
+ * Write the stored column widths into the width class that is active now.
386
+ *
387
+ * Every width class has its own default widths, and the class of the object browser is only
388
+ * known after its container was measured - in the constructor it is still the initial guess.
389
+ * The stored widths must therefore be applied again whenever the class changes, otherwise the
390
+ * table falls back to the defaults of the new class and the user sees their widths reset.
391
+ */
392
+ applyStoredWidths() {
393
+ if (!this.storedColumnWidths) {
394
+ return;
395
+ }
396
+ const screen = this.screenWidths[this.width || 'lg'];
397
+ const storedColumnWidths = this.storedColumnWidths;
398
+ Object.keys(storedColumnWidths).forEach(id => {
399
+ if (id === 'id') {
400
+ screen.idWidth = storedColumnWidths.id;
401
+ }
402
+ else if (screen.widths[id] !== undefined) {
403
+ // A column that this width class does not know keeps its place; only the widths of
404
+ // the columns the class actually shows may be overwritten.
405
+ screen.widths[id] = storedColumnWidths[id];
406
+ }
407
+ });
408
+ this.customWidth = true;
409
+ }
381
410
  async loadAllObjects(update) {
382
411
  const props = this.props;
383
412
  try {
@@ -1873,30 +1902,45 @@ export class ObjectBrowserClass extends Component {
1873
1902
  }
1874
1903
  else {
1875
1904
  const width = this.width || 'lg';
1905
+ /**
1906
+ * A row shows either type/role/room/function or the four columns of the states view,
1907
+ * never both. A column of the set that is not shown must have the width zero, so that
1908
+ * the ID column, which fills the rest of the row, gets the right place.
1909
+ */
1910
+ const shown = (name, inStatesView) => !!this.state.statesView === inStatesView && columns.includes(name);
1911
+ /** Width of one column: what the user set for it, else the default of the width class */
1912
+ const widthOf = (name) => columnsWidths[name] || WIDTHS[name] || 0;
1913
+ /**
1914
+ * The width classes below `lg` do not define the columns of the states view, but the
1915
+ * user can select them by hand there too, so they fall back to the widest class.
1916
+ */
1917
+ const statesWidthOf = (name) => widthOf(name) || this.screenWidths.xl.widths[name] || 0;
1876
1918
  this.columnsVisibility = {
1877
1919
  id: columnsWidths.id || this.screenWidths[width].idWidth,
1878
- name: columns.includes('name')
1879
- ? columnsWidths.name || WIDTHS.name || this.screenWidths[width].widths.name || 0
1880
- : 0,
1881
- type: columns.includes('type')
1882
- ? columnsWidths.type || WIDTHS.type || this.screenWidths[width].widths.type || 0
1883
- : 0,
1884
- role: columns.includes('role')
1885
- ? columnsWidths.role || WIDTHS.role || this.screenWidths[width].widths.role || 0
1886
- : 0,
1887
- room: columns.includes('room')
1888
- ? columnsWidths.room || WIDTHS.room || this.screenWidths[width].widths.room || 0
1889
- : 0,
1890
- func: columns.includes('func')
1891
- ? columnsWidths.func || WIDTHS.func || this.screenWidths[width].widths.func || 0
1892
- : 0,
1920
+ name: columns.includes('name') ? widthOf('name') : 0,
1921
+ type: shown('type', false) ? widthOf('type') : 0,
1922
+ role: shown('role', false) ? widthOf('role') : 0,
1923
+ room: shown('room', false) ? widthOf('room') : 0,
1924
+ func: shown('func', false) ? widthOf('func') : 0,
1925
+ // The states view columns were missing here completely: their checkboxes in the
1926
+ // configuration dialog had no effect, and the header showed them although no row
1927
+ // had a cell for them.
1928
+ changedFrom: shown('changedFrom', true) ? statesWidthOf('changedFrom') : 0,
1929
+ qualityCode: shown('qualityCode', true) ? statesWidthOf('qualityCode') : 0,
1930
+ timestamp: shown('timestamp', true) ? statesWidthOf('timestamp') : 0,
1931
+ lastChange: shown('lastChange', true) ? statesWidthOf('lastChange') : 0,
1893
1932
  };
1894
1933
  let widthSum = this.columnsVisibility.id; // id is always visible
1895
1934
  if (this.columnsVisibility.name) {
1935
+ // Only one of the two sets has a width, so both may be added up
1896
1936
  widthSum += this.columnsVisibility.type || 0;
1897
1937
  widthSum += this.columnsVisibility.role || 0;
1898
1938
  widthSum += this.columnsVisibility.room || 0;
1899
1939
  widthSum += this.columnsVisibility.func || 0;
1940
+ widthSum += this.columnsVisibility.changedFrom || 0;
1941
+ widthSum += this.columnsVisibility.qualityCode || 0;
1942
+ widthSum += this.columnsVisibility.timestamp || 0;
1943
+ widthSum += this.columnsVisibility.lastChange || 0;
1900
1944
  }
1901
1945
  if (columnsForAdmin && columns) {
1902
1946
  Object.keys(columnsForAdmin)
@@ -2049,6 +2093,10 @@ export class ObjectBrowserClass extends Component {
2049
2093
  }
2050
2094
  };
2051
2095
  resizerMouseUp = () => {
2096
+ // Remember the new widths for the other width classes as well: the drag only changed two CSS
2097
+ // variables, the width class of the container may change at any time afterwards.
2098
+ this.storedColumnWidths = { ...this.resizerCurrentWidths };
2099
+ this.applyStoredWidths();
2052
2100
  this.localStorage.setItem(`${this.props.dialogName || 'App'}.table`, JSON.stringify(this.resizerCurrentWidths));
2053
2101
  this.resizerActiveName = null;
2054
2102
  this.resizerNextName = null;
@@ -2058,7 +2106,6 @@ export class ObjectBrowserClass extends Component {
2058
2106
  window.removeEventListener('mouseup', this.resizerMouseUp);
2059
2107
  };
2060
2108
  resizerMouseDown = (e) => {
2061
- this.storedWidths ||= JSON.parse(JSON.stringify(this.screenWidths[this.width || 'lg']));
2062
2109
  this.resizerCurrentWidths ||= {};
2063
2110
  this.resizerActiveDiv = e.target.parentNode;
2064
2111
  this.resizerActiveName = this.resizerActiveDiv.dataset.name || null;
@@ -2085,6 +2132,12 @@ export class ObjectBrowserClass extends Component {
2085
2132
  i++;
2086
2133
  } */
2087
2134
  }
2135
+ if (!this.resizerNextDiv) {
2136
+ // The column is the last one of the row, there is no neighbour to take the place from
2137
+ this.resizerActiveDiv = null;
2138
+ this.resizerActiveName = null;
2139
+ return;
2140
+ }
2088
2141
  this.resizerNextName = this.resizerNextDiv.dataset.name || null;
2089
2142
  this.resizerMin = parseInt(this.resizerActiveDiv.dataset.min, 10) || 0;
2090
2143
  this.resizerNextMin = parseInt(this.resizerNextDiv.dataset.min, 10) || 0;
@@ -2167,7 +2220,13 @@ export class ObjectBrowserClass extends Component {
2167
2220
  }
2168
2221
  resizerReset = () => {
2169
2222
  this.customWidth = false;
2170
- this.screenWidths[this.width || 'lg'] = JSON.parse(JSON.stringify(this.storedWidths));
2223
+ this.storedColumnWidths = null;
2224
+ this.resizerCurrentWidths = {};
2225
+ // Back to the defaults of every width class, not only of the one that is active now - the
2226
+ // stored widths had been applied to every class the user had the browser open in.
2227
+ Object.keys(SCREEN_WIDTHS).forEach(width => {
2228
+ this.screenWidths[width] = JSON.parse(JSON.stringify(SCREEN_WIDTHS[width]));
2229
+ });
2171
2230
  this.calculateColumnsVisibility();
2172
2231
  this.localStorage.removeItem(`${this.props.dialogName || 'App'}.table`);
2173
2232
  this.forceUpdate();
@@ -2381,9 +2440,13 @@ export class ObjectBrowserClass extends Component {
2381
2440
  if (!this.state.loaded) {
2382
2441
  return React.createElement(LinearProgress, { key: `${this.props.dialogName}_c` });
2383
2442
  }
2384
- // the container was resized into another width class => other columns are visible
2385
- if (this.lastCalculatedWidth !== this.width) {
2443
+ // the container was resized into another width class => other columns are visible, and the
2444
+ // stored widths have to be written into the defaults of the new class.
2445
+ // The states view shows other columns too, so it has to be recalculated as well.
2446
+ if (this.lastCalculatedWidth !== this.width || this.lastCalculatedStatesView !== this.state.statesView) {
2386
2447
  this.lastCalculatedWidth = this.width;
2448
+ this.lastCalculatedStatesView = this.state.statesView;
2449
+ this.applyStoredWidths();
2387
2450
  this.calculateColumnsVisibility();
2388
2451
  }
2389
2452
  const items = this.root ? this.renderItem(this.root, undefined) : null;