@iobroker/gui-components 10.0.16 → 10.2.0

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 (28) hide show
  1. package/README.md +36 -1
  2. package/build/Components/DeviceType/DeviceTypeIcon.js +34 -10
  3. package/build/Components/DeviceType/DeviceTypeIcon.js.map +1 -1
  4. package/build/Components/DeviceType/deviceTypeTranslations.js +2 -0
  5. package/build/Components/DeviceType/deviceTypeTranslations.js.map +1 -1
  6. package/build/Components/DeviceType/i18n/de.json +11 -2
  7. package/build/Components/DeviceType/i18n/en.json +11 -2
  8. package/build/Components/DeviceType/i18n/es.json +56 -0
  9. package/build/Components/DeviceType/i18n/fr.json +11 -2
  10. package/build/Components/DeviceType/i18n/it.json +11 -2
  11. package/build/Components/DeviceType/i18n/nl.json +11 -2
  12. package/build/Components/DeviceType/i18n/pl.json +11 -2
  13. package/build/Components/DeviceType/i18n/pt.json +11 -2
  14. package/build/Components/DeviceType/i18n/ru.json +11 -2
  15. package/build/Components/DeviceType/i18n/uk.json +11 -2
  16. package/build/Components/DeviceType/i18n/zh-cn.json +11 -2
  17. package/build/Components/ObjectBrowser/ObjectBrowserClass.d.ts +6 -2
  18. package/build/Components/ObjectBrowser/ObjectBrowserClass.js +73 -35
  19. package/build/Components/ObjectBrowser/ObjectBrowserClass.js.map +1 -1
  20. package/build/Components/ObjectBrowser/dialogs.js +1 -1
  21. package/build/Components/ObjectBrowser/dialogs.js.map +1 -1
  22. package/build/Components/ObjectBrowser/renderLeaf.js +5 -11
  23. package/build/Components/ObjectBrowser/renderLeaf.js.map +1 -1
  24. package/build/Components/ObjectBrowser/styles.js +9 -0
  25. package/build/Components/ObjectBrowser/styles.js.map +1 -1
  26. package/build/Components/ObjectBrowser/utils.js +5 -12
  27. package/build/Components/ObjectBrowser/utils.js.map +1 -1
  28. package/package.json +10 -8
@@ -1,6 +1,8 @@
1
1
  {
2
2
  "type-Device type": "Тип устройства",
3
3
  "type-airCondition": "Кондиционер",
4
+ "type-airPurifier": "Очиститель воздуха",
5
+ "type-airQuality": "Качество воздуха",
4
6
  "type-blind": "Рольставни/Жалюзи",
5
7
  "type-blindButtons": "Жалюзи управляющиеся кнопками",
6
8
  "type-button": "Кнопка",
@@ -8,11 +10,17 @@
8
10
  "type-camera": "Камера",
9
11
  "type-chart": "Диаграмма",
10
12
  "type-cie": "CIE свет",
13
+ "type-coAlarm": "Датчик угарного газа",
14
+ "type-contact": "Датчик контакта",
11
15
  "type-ct": "Цветовая температура",
12
16
  "type-dimmer": "Диммер",
13
17
  "type-door": "Датчик положения двери",
18
+ "type-electricity": "Счётчик электроэнергии",
19
+ "type-fan": "Вентилятор",
20
+ "type-fillLevel": "Уровень наполнения",
14
21
  "type-fireAlarm": "Датчик дыма",
15
22
  "type-floodAlarm": "Датчик затопления",
23
+ "type-flow": "Расход",
16
24
  "type-gate": "Ворота",
17
25
  "type-hue": "HUE свет",
18
26
  "type-humidity": "Влажность",
@@ -27,6 +35,8 @@
27
35
  "type-media": "Медиа-плейер",
28
36
  "type-motion": "Датчик движения",
29
37
  "type-percentage": "Проценты",
38
+ "type-pressure": "Давление",
39
+ "type-pump": "Насос",
30
40
  "type-rgb": "RGB свет",
31
41
  "type-rgbSingle": "R,G,B как отдельные значение",
32
42
  "type-rgbwSingle": "R,G,B,W как отдельные значение",
@@ -34,9 +44,8 @@
34
44
  "type-socket": "Розетка",
35
45
  "type-temperature": "Температура",
36
46
  "type-thermostat": "Термостат",
37
- "type-url": "URL",
47
+ "type-unknown": "Неизвестно",
38
48
  "type-vacuumCleaner": "Пылесос",
39
- "type-valve": "Вентиль",
40
49
  "type-volume": "Громкость",
41
50
  "type-volumeGroup": "Группа громкости",
42
51
  "type-warning": "Предупреждение",
@@ -1,6 +1,8 @@
1
1
  {
2
2
  "type-Device type": "Тип пристрою",
3
3
  "type-airCondition": "Кондиціонер",
4
+ "type-airPurifier": "Очищувач повітря",
5
+ "type-airQuality": "Якість повітря",
4
6
  "type-blind": "жалюзі",
5
7
  "type-blindButtons": "Штора керується кнопками",
6
8
  "type-button": "кнопку",
@@ -8,11 +10,17 @@
8
10
  "type-camera": "камера",
9
11
  "type-chart": "Діаграма",
10
12
  "type-cie": "Кольорове світло CIE",
13
+ "type-coAlarm": "Датчик чадного газу",
14
+ "type-contact": "Датчик контакту",
11
15
  "type-ct": "Колірна температура",
12
16
  "type-dimmer": "диммер",
13
17
  "type-door": "двері",
18
+ "type-electricity": "Лічильник електроенергії",
19
+ "type-fan": "Вентилятор",
20
+ "type-fillLevel": "Рівень наповнення",
14
21
  "type-fireAlarm": "пожежна сигналізація",
15
22
  "type-floodAlarm": "сигналізація про повінь",
23
+ "type-flow": "Витрата",
16
24
  "type-gate": "ворота",
17
25
  "type-hue": "світло HUE",
18
26
  "type-humidity": "вологість",
@@ -27,6 +35,8 @@
27
35
  "type-media": "ЗМІ",
28
36
  "type-motion": "руху",
29
37
  "type-percentage": "Відсоток",
38
+ "type-pressure": "Тиск",
39
+ "type-pump": "Насос",
30
40
  "type-rgb": "RGB світло",
31
41
  "type-rgbSingle": "Одиночне світло RGB",
32
42
  "type-rgbwSingle": "RGBW як одне значення",
@@ -34,9 +44,8 @@
34
44
  "type-socket": "гніздо",
35
45
  "type-temperature": "температура",
36
46
  "type-thermostat": "термостат",
37
- "type-url": "URL",
47
+ "type-unknown": "Невідомо",
38
48
  "type-vacuumCleaner": "Пилосос",
39
- "type-valve": "клапан",
40
49
  "type-volume": "обсяг",
41
50
  "type-volumeGroup": "група обсягів",
42
51
  "type-warning": "УВАГА",
@@ -1,6 +1,8 @@
1
1
  {
2
2
  "type-Device type": "设备类型",
3
3
  "type-airCondition": "空调",
4
+ "type-airPurifier": "空气净化器",
5
+ "type-airQuality": "空气质量",
4
6
  "type-blind": "百叶窗",
5
7
  "type-blindButtons": "按钮控制百叶窗",
6
8
  "type-button": "按钮",
@@ -8,11 +10,17 @@
8
10
  "type-camera": "相机",
9
11
  "type-chart": "图表",
10
12
  "type-cie": "CIE色光",
13
+ "type-coAlarm": "一氧化碳报警器",
14
+ "type-contact": "触点传感器",
11
15
  "type-ct": "色温",
12
16
  "type-dimmer": "调光器",
13
17
  "type-door": "门",
18
+ "type-electricity": "电表",
19
+ "type-fan": "风扇",
20
+ "type-fillLevel": "填充量",
14
21
  "type-fireAlarm": "火灾报警",
15
22
  "type-floodAlarm": "洪水警报",
23
+ "type-flow": "流量",
16
24
  "type-gate": "门",
17
25
  "type-hue": "色调灯",
18
26
  "type-humidity": "湿度",
@@ -27,6 +35,8 @@
27
35
  "type-media": "媒体",
28
36
  "type-motion": "运动",
29
37
  "type-percentage": "百分比",
38
+ "type-pressure": "压力",
39
+ "type-pump": "泵",
30
40
  "type-rgb": "RGB灯",
31
41
  "type-rgbSingle": "RGB灯单",
32
42
  "type-rgbwSingle": "RGBW 作为单一值",
@@ -34,9 +44,8 @@
34
44
  "type-socket": "插座",
35
45
  "type-temperature": "温度",
36
46
  "type-thermostat": "恒温器",
37
- "type-url": "网址",
47
+ "type-unknown": "未知",
38
48
  "type-vacuumCleaner": "吸尘器",
39
- "type-valve": "阀门",
40
49
  "type-volume": "体积",
41
50
  "type-volumeGroup": "卷组",
42
51
  "type-warning": "警告",
@@ -33,6 +33,10 @@ export declare class ObjectBrowserClass extends Component<ObjectBrowserProps, Ob
33
33
  /** Width class for which the columns were calculated the last time */
34
34
  private lastCalculatedWidth;
35
35
  private pausedSubscribes;
36
+ /** The tree was rebuilt while subscribes were paused, so the filter must be re-applied on resume */
37
+ private treeRebuiltWhilePaused;
38
+ /** Time of the first object change since the last tree rebuild (upper bound for the rebuild delay) */
39
+ private objectsUpdateFirstTs;
36
40
  private selectFirst;
37
41
  /** Last navigation that was applied from `navigateTo` or reported via `onNavigateTo` (loop guard). */
38
42
  private lastNav;
@@ -40,7 +44,7 @@ export declare class ObjectBrowserClass extends Component<ObjectBrowserProps, Ob
40
44
  private applyingNav;
41
45
  root: TreeItem | null;
42
46
  readonly states: Record<string, ioBroker.State>;
43
- subscribes: string[];
47
+ subscribes: Set<string>;
44
48
  private unsubscribeTimer;
45
49
  private statesUpdateTimer;
46
50
  private objectsUpdateTimer;
@@ -94,7 +98,7 @@ export declare class ObjectBrowserClass extends Component<ObjectBrowserProps, Ob
94
98
  item: any;
95
99
  ts: number;
96
100
  };
97
- recordStates: string[];
101
+ recordStates: Set<string>;
98
102
  styles: {
99
103
  cellIdIconFolder?: React.CSSProperties;
100
104
  cellIdIconDocument?: React.CSSProperties;
@@ -17,7 +17,7 @@ import { Utils } from '../Utils'; // @iobroker/gui-components/Components/Utils
17
17
  import { TabContainer } from '../TabContainer';
18
18
  import { TabContent } from '../TabContent';
19
19
  import { TabHeader } from '../TabHeader';
20
- import { applyFilter, binarySearch, buildTree, findNode, formatValue, generateFile, getName, colVar, widthFromContainer, growVar, getSelectIdIconFromObjects, setCustomValue, } from './utils';
20
+ import { applyFilter, buildTree, findNode, formatValue, generateFile, getName, colVar, widthFromContainer, growVar, getSelectIdIconFromObjects, setCustomValue, } from './utils';
21
21
  import { HistoryChart } from './HistoryChart';
22
22
  import { styles } from './styles';
23
23
  import * as dialogs from './dialogs';
@@ -54,6 +54,10 @@ export class ObjectBrowserClass extends Component {
54
54
  /** Width class for which the columns were calculated the last time */
55
55
  lastCalculatedWidth = null;
56
56
  pausedSubscribes = false;
57
+ /** The tree was rebuilt while subscribes were paused, so the filter must be re-applied on resume */
58
+ treeRebuiltWhilePaused = false;
59
+ /** Time of the first object change since the last tree rebuild (upper bound for the rebuild delay) */
60
+ objectsUpdateFirstTs = 0;
57
61
  selectFirst;
58
62
  /** Last navigation that was applied from `navigateTo` or reported via `onNavigateTo` (loop guard). */
59
63
  lastNav = null;
@@ -61,7 +65,7 @@ export class ObjectBrowserClass extends Component {
61
65
  applyingNav = false;
62
66
  root = null;
63
67
  states = {};
64
- subscribes = [];
68
+ subscribes = new Set();
65
69
  unsubscribeTimer = null;
66
70
  statesUpdateTimer = null;
67
71
  objectsUpdateTimer = null;
@@ -99,7 +103,7 @@ export class ObjectBrowserClass extends Component {
99
103
  columnsVisibility = {};
100
104
  changedIds = null;
101
105
  contextMenu = null;
102
- recordStates = [];
106
+ recordStates = new Set();
103
107
  styles = {};
104
108
  expertMode = false;
105
109
  customColumnDialog = null;
@@ -688,7 +692,7 @@ export class ObjectBrowserClass extends Component {
688
692
  // console.log(`- unsubscribe ${pattern}`);
689
693
  this.props.socket.unsubscribeState(pattern, this.onStateChange);
690
694
  });
691
- this.subscribes = [];
695
+ this.subscribes.clear();
692
696
  this.objects = {};
693
697
  }
694
698
  /**
@@ -740,7 +744,7 @@ export class ObjectBrowserClass extends Component {
740
744
  // console.log(`- unsubscribe ${pattern}`);
741
745
  this.props.socket.unsubscribeState(pattern, this.onStateChange);
742
746
  });
743
- this.subscribes = [];
747
+ this.subscribes.clear();
744
748
  this.loadAllObjects(true)
745
749
  .then(() => console.log('updated!'))
746
750
  .catch(e => this.showError(e));
@@ -837,12 +841,12 @@ export class ObjectBrowserClass extends Component {
837
841
  }
838
842
  checkUnsubscribes() {
839
843
  // Remove unused subscriptions
840
- for (let i = this.subscribes.length - 1; i >= 0; i--) {
841
- if (!this.recordStates.includes(this.subscribes[i])) {
842
- this.unsubscribe(this.subscribes[i]);
844
+ for (const id of [...this.subscribes]) {
845
+ if (!this.recordStates.has(id)) {
846
+ this.unsubscribe(id);
843
847
  }
844
848
  }
845
- this.recordStates = [];
849
+ this.recordStates.clear();
846
850
  }
847
851
  /**
848
852
  * Find an item.
@@ -875,6 +879,19 @@ export class ObjectBrowserClass extends Component {
875
879
  */
876
880
  onStateChange = (id, state) => {
877
881
  // console.log(`> stateChange ${id}`);
882
+ const oldState = this.states[id];
883
+ if (state &&
884
+ oldState &&
885
+ state.val === oldState.val &&
886
+ state.ack === oldState.ack &&
887
+ state.q === oldState.q &&
888
+ state.ts === oldState.ts &&
889
+ state.lc === oldState.lc &&
890
+ state.from === oldState.from) {
891
+ // echo of the already known state: nothing that is displayed can have changed
892
+ // (`from` is compared too, because it is shown in the `changedFrom` column)
893
+ return;
894
+ }
878
895
  if (this.states[id]) {
879
896
  const item = this.findItem(id);
880
897
  if (item?.data.state) {
@@ -995,23 +1012,38 @@ export class ObjectBrowserClass extends Component {
995
1012
  this.afterObjectUpdated();
996
1013
  };
997
1014
  afterObjectUpdated() {
998
- if (!this.objectsUpdateTimer && this.objects) {
999
- this.objectsUpdateTimer = setTimeout(() => {
1000
- this.objectsUpdateTimer = null;
1001
- const { info, root } = buildTree(this.objects, {
1002
- imagePrefix: this.props.imagePrefix,
1003
- root: this.props.root,
1004
- lang: this.props.lang,
1005
- themeType: this.props.themeType,
1006
- });
1007
- this.root = root;
1008
- this.info = info;
1009
- if (!this.pausedSubscribes) {
1010
- this.doFilter();
1011
- }
1012
- // else it will be re-rendered when the dialog will be closed
1013
- }, 500);
1015
+ if (!this.objects) {
1016
+ return;
1017
+ }
1018
+ if (this.objectsUpdateTimer) {
1019
+ // collect a burst of object changes into one rebuild: wait for a quiet period,
1020
+ // but do not postpone the rebuild forever while changes keep arriving
1021
+ if (Date.now() - this.objectsUpdateFirstTs >= 2000) {
1022
+ return;
1023
+ }
1024
+ clearTimeout(this.objectsUpdateTimer);
1025
+ }
1026
+ else {
1027
+ this.objectsUpdateFirstTs = Date.now();
1014
1028
  }
1029
+ this.objectsUpdateTimer = setTimeout(() => {
1030
+ this.objectsUpdateTimer = null;
1031
+ const { info, root } = buildTree(this.objects, {
1032
+ imagePrefix: this.props.imagePrefix,
1033
+ root: this.props.root,
1034
+ lang: this.props.lang,
1035
+ themeType: this.props.themeType,
1036
+ });
1037
+ this.root = root;
1038
+ this.info = info;
1039
+ if (!this.pausedSubscribes) {
1040
+ this.doFilter(true);
1041
+ }
1042
+ else {
1043
+ // the new tree has no visibility flags yet; re-apply the filter when the dialog is closed
1044
+ this.treeRebuiltWhilePaused = true;
1045
+ }
1046
+ }, 500);
1015
1047
  }
1016
1048
  // This function is called when the user changes the alias of an object.
1017
1049
  // It updates the aliasMap and returns true if the aliasMap has changed.
@@ -1097,8 +1129,8 @@ export class ObjectBrowserClass extends Component {
1097
1129
  return { newInnerState, filtered: false };
1098
1130
  }
1099
1131
  subscribe(id) {
1100
- if (!this.subscribes.includes(id)) {
1101
- this.subscribes.push(id);
1132
+ if (!this.subscribes.has(id)) {
1133
+ this.subscribes.add(id);
1102
1134
  // console.log(`+ subscribe ${id}`);
1103
1135
  if (!this.pausedSubscribes) {
1104
1136
  this.props.socket
@@ -1108,9 +1140,8 @@ export class ObjectBrowserClass extends Component {
1108
1140
  }
1109
1141
  }
1110
1142
  unsubscribe(id) {
1111
- const pos = this.subscribes.indexOf(id);
1112
- if (pos !== -1) {
1113
- this.subscribes.splice(pos, 1);
1143
+ if (this.subscribes.has(id)) {
1144
+ this.subscribes.delete(id);
1114
1145
  if (this.states[id]) {
1115
1146
  delete this.states[id];
1116
1147
  }
@@ -1129,6 +1160,11 @@ export class ObjectBrowserClass extends Component {
1129
1160
  else if (this.pausedSubscribes && !isPause) {
1130
1161
  this.pausedSubscribes = false;
1131
1162
  this.subscribes.forEach(id => this.props.socket.subscribeState(id, this.onStateChange));
1163
+ if (this.treeRebuiltWhilePaused) {
1164
+ // without this filter run, no node of the rebuilt tree is marked visible and the browser stays empty
1165
+ this.treeRebuiltWhilePaused = false;
1166
+ this.doFilter(true);
1167
+ }
1132
1168
  }
1133
1169
  }
1134
1170
  clearFilter() {
@@ -1168,20 +1204,21 @@ export class ObjectBrowserClass extends Component {
1168
1204
  this.localStorage.setItem(`${this.props.dialogName || 'App'}.objectSelected`, '[]');
1169
1205
  this.setState({ expanded: [], depth: 0, selected: [] }, () => this.onAfterSelect());
1170
1206
  }
1171
- expandDepth(root, depth, expanded) {
1207
+ expandDepth(root, depth, expanded, expandedSet) {
1172
1208
  if (!this.root) {
1173
1209
  throw new Error('No root');
1174
1210
  }
1175
1211
  root ||= this.root;
1212
+ expandedSet ||= new Set(expanded);
1176
1213
  if (depth > 0) {
1177
1214
  root.children?.forEach(item => {
1178
1215
  if (item.data.sumVisibility) {
1179
- if (!binarySearch(expanded, item.data.id)) {
1216
+ if (!expandedSet.has(item.data.id)) {
1180
1217
  expanded.push(item.data.id);
1181
- expanded.sort();
1218
+ expandedSet.add(item.data.id);
1182
1219
  }
1183
1220
  if (depth - 1 > 0) {
1184
- this.expandDepth(item, depth - 1, expanded);
1221
+ this.expandDepth(item, depth - 1, expanded, expandedSet);
1185
1222
  }
1186
1223
  }
1187
1224
  });
@@ -1196,6 +1233,7 @@ export class ObjectBrowserClass extends Component {
1196
1233
  const expanded = [...this.state.expanded];
1197
1234
  if (this.root) {
1198
1235
  this.expandDepth(this.root, depth, expanded);
1236
+ expanded.sort();
1199
1237
  }
1200
1238
  this.localStorage.setItem(`${this.props.dialogName || 'App'}.objectExpanded`, JSON.stringify(expanded));
1201
1239
  this.setState({ depth, expanded });
@@ -2303,7 +2341,7 @@ export class ObjectBrowserClass extends Component {
2303
2341
  * The rendering method of this component.
2304
2342
  */
2305
2343
  render() {
2306
- this.recordStates = [];
2344
+ this.recordStates.clear();
2307
2345
  if (this.unsubscribeTimer) {
2308
2346
  clearTimeout(this.unsubscribeTimer);
2309
2347
  }