@iobroker/json-config 9.0.23 → 9.1.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.
- package/README.md +48 -3
- package/build/JsonConfigComponent/ConfigGeneric.d.ts +67 -5
- package/build/JsonConfigComponent/ConfigGeneric.js +164 -20
- package/build/JsonConfigComponent/ConfigGeneric.js.map +1 -1
- package/build/JsonConfigComponent/ConfigState.js +16 -1
- package/build/JsonConfigComponent/ConfigState.js.map +1 -1
- package/build/JsonConfigComponent/ConfigTabs.d.ts +9 -0
- package/build/JsonConfigComponent/ConfigTabs.js +37 -5
- package/build/JsonConfigComponent/ConfigTabs.js.map +1 -1
- package/build/JsonConfigComponent/index.d.ts +3 -0
- package/build/JsonConfigComponent/index.js +11 -0
- package/build/JsonConfigComponent/index.js.map +1 -1
- package/build/JsonConfigComponent/statePool.d.ts +60 -0
- package/build/JsonConfigComponent/statePool.js +172 -0
- package/build/JsonConfigComponent/statePool.js.map +1 -0
- package/build/types.d.ts +27 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1503,6 +1503,7 @@ In the Settings of the Web developer tools, you can create your own devices with
|
|
|
1503
1503
|
| `notOs` | Do not show this element on these operating systems of the host, on which the instance runs: `"win32"` or `["linux", "darwin"]` |
|
|
1504
1504
|
| `docker` | Show this element only if the ioBroker runs (`true`) or does not run (`false`) in a docker container |
|
|
1505
1505
|
| `disabled` | JS function that could use `native.attribute` for calculation |
|
|
1506
|
+
| `dependsOnStates` | ioBroker states, on which this element depends: `{"running": ".info.browsing"}`. See [Show or disable elements depending on ioBroker states](#show-or-disable-elements-depending-on-iobroker-states) |
|
|
1506
1507
|
| `help` | help text (multi-language) |
|
|
1507
1508
|
| `helpLink` | href to help (could be used only together with `help`) |
|
|
1508
1509
|
| `style` | CSS style in ReactJS notation: `radiusBorder` and not `radius-border`. |
|
|
@@ -1558,6 +1559,42 @@ the text patterns of `label`, `help` and so on:
|
|
|
1558
1559
|
}
|
|
1559
1560
|
```
|
|
1560
1561
|
|
|
1562
|
+
### Show or disable elements depending on ioBroker states
|
|
1563
|
+
|
|
1564
|
+
With `dependsOnStates`, an element can react on the values of ioBroker states. The states are subscribed,
|
|
1565
|
+
so the element is updated immediately if a state changes - no reload of the configuration dialog is required.
|
|
1566
|
+
|
|
1567
|
+
```json5
|
|
1568
|
+
{
|
|
1569
|
+
"startBrowse": {
|
|
1570
|
+
"type": "sendTo",
|
|
1571
|
+
"command": "browse",
|
|
1572
|
+
"label": "${_states.running?.val ? 'Stop browse' : 'Start browse'}",
|
|
1573
|
+
"dependsOnStates": { "running": ".info.browsing" },
|
|
1574
|
+
"disabled": "!!_states.running?.val"
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
1577
|
+
```
|
|
1578
|
+
|
|
1579
|
+
- `dependsOnStates` is written as `{"<alias>": "<state ID>"}`. The values are available in **all** JS functions
|
|
1580
|
+
(`hidden`, `disabled`, `validator`, `defaultFunc`, `onChange.calculateFunc`, `confirm.condition`) and in the
|
|
1581
|
+
text patterns of `label`, `help`, `tooltip` and so on as `_states.<alias>`.
|
|
1582
|
+
- `_states.<alias>` contains the **whole state object**, so `_states.running?.val`, `_states.running?.ts`,
|
|
1583
|
+
`_states.running?.ack` can be used. It is `null` if the state does not exist, so always use `?.`.
|
|
1584
|
+
- A state ID that starts with a dot addresses the own instance: `.info.browsing` => `myAdapter.0.info.browsing`.
|
|
1585
|
+
Every other ID is used as it is, so the states of other adapters can be monitored too.
|
|
1586
|
+
- The ID may contain `${data.xxx}` patterns, e.g. `"device": "${data.deviceInstance}.info.connection"`.
|
|
1587
|
+
It will be resolved anew if the configuration changes. Wildcards (`*`) are **not** allowed.
|
|
1588
|
+
- If one of the states changes, `hidden`, `disabled`, `label`, `help`, `validator` and `defaultFunc` of this
|
|
1589
|
+
element will be calculated anew. Every state is subscribed only once, independent of how many elements
|
|
1590
|
+
(or table lines) use it.
|
|
1591
|
+
- The short form `"dependsOnStates": ["admin.0.info.connection"]` uses the ID itself as an alias:
|
|
1592
|
+
`_states['admin.0.info.connection']`.
|
|
1593
|
+
- The attribute may be used on every element, also on `panel`, `tabs` and table columns.
|
|
1594
|
+
|
|
1595
|
+
**Note:** old admin versions do not know `_states` and would throw an error by the evaluation of such a
|
|
1596
|
+
function, so the element would stay visible and enabled.
|
|
1597
|
+
|
|
1561
1598
|
**Note:** old admin versions do not know `_os` and would evaluate `"hidden": "_os !== 'linux'"` to `true`
|
|
1562
1599
|
and so hide the element everywhere. Because of that, `os`/`notOs` should be preferred, as they are simply
|
|
1563
1600
|
ignored by old admin versions (the element will be shown). If a JS function must be used, write it
|
|
@@ -1708,6 +1745,7 @@ const func = new Function(
|
|
|
1708
1745
|
'_os', // Operating system of the host, where the instance runs: 'win32', 'linux', 'darwin', ...
|
|
1709
1746
|
'_arch', // Architecture of the host, where the instance runs: 'x64', 'arm64', ...
|
|
1710
1747
|
'_host', // Information about the host: {id, os, osType, arch, release, nodeVersion, controllerVersion, docker, dockerVersion}
|
|
1748
|
+
'_states', // Values of the states from `dependsOnStates`: {<alias>: <state object or null>}
|
|
1711
1749
|
myValidator.includes('return') ? myValidator : 'return ' + myValidator); // e.g. "_alive === true"
|
|
1712
1750
|
|
|
1713
1751
|
const isValid = func(data, systemConfig.common, instanceAlive, adapter.common, this.props.socket);
|
|
@@ -1728,6 +1766,7 @@ The following variables are available in JS function in adapter settings:
|
|
|
1728
1766
|
- `_os` - operating system of the host, on which the instance runs (`process.platform`), e.g. `linux`, `win32`, `darwin`. Empty string if unknown
|
|
1729
1767
|
- `_arch` - architecture of the host, on which the instance runs, e.g. `x64`, `arm64`
|
|
1730
1768
|
- `_host` - information about the host: `{id, os, osType, arch, release, nodeVersion, controllerVersion, docker, dockerVersion}`. `docker` is `undefined` if the docker state was not requested or the host did not answer
|
|
1769
|
+
- `_states` - values of the states from [`dependsOnStates`](#show-or-disable-elements-depending-on-iobroker-states): `{<alias>: <state object>}`. `null` if the state does not exist
|
|
1731
1770
|
|
|
1732
1771
|
### Custom settings dialog
|
|
1733
1772
|
|
|
@@ -1748,6 +1787,7 @@ const func = new Function(
|
|
|
1748
1787
|
"_os",
|
|
1749
1788
|
"_arch",
|
|
1750
1789
|
"_host",
|
|
1790
|
+
"_states",
|
|
1751
1791
|
myValidator.includes("return") ? myValidator : "return " + myValidator
|
|
1752
1792
|
); // e.g. "_alive === true"
|
|
1753
1793
|
|
|
@@ -1774,6 +1814,7 @@ The following variables are available in JS function in custom settings:
|
|
|
1774
1814
|
- `_os` - operating system of the host, on which the instance runs (`process.platform`), e.g. `linux`, `win32`, `darwin`. Empty string if unknown
|
|
1775
1815
|
- `_arch` - architecture of the host, on which the instance runs, e.g. `x64`, `arm64`
|
|
1776
1816
|
- `_host` - information about the host: `{id, os, osType, arch, release, nodeVersion, controllerVersion, docker, dockerVersion}`. `docker` is `undefined` if the docker state was not requested or the host did not answer
|
|
1817
|
+
- `_states` - values of the states from [`dependsOnStates`](#show-or-disable-elements-depending-on-iobroker-states): `{<alias>: <state object>}`. `null` if the state does not exist
|
|
1777
1818
|
|
|
1778
1819
|
```json5
|
|
1779
1820
|
{
|
|
@@ -1907,10 +1948,14 @@ The schema is used here: https://github.com/SchemaStore/schemastore/blob/6da29cd
|
|
|
1907
1948
|
### **WORK IN PROGRESS**
|
|
1908
1949
|
-->
|
|
1909
1950
|
## Changelog
|
|
1951
|
+
### 9.1.0 (2026-08-31)
|
|
1952
|
+
- (@GermanBluefox) Added progress bar to the state component
|
|
1953
|
+
- (@GermanBluefox) Added the possibility to show or hide elements depending on the states: `dependsOnStates` and the JS variable `_states`
|
|
1954
|
+
|
|
1910
1955
|
### 9.0.23 (2026-08-27)
|
|
1911
|
-
(@krobipd) Corrected: the object browser stayed empty after closing the object customization dialog if any object was changed while the dialog was open (ioBroker/ioBroker.admin#3391)
|
|
1912
|
-
(@krobipd) Changed: `ObjectBrowserClass.subscribes` and `.recordStates` are Sets instead of arrays now
|
|
1913
|
-
(@krobipd) Improved: object browser performance on large installations — bursts of object changes cause one tree rebuild instead of several, state-change echoes no longer trigger redraws, subscription bookkeeping is no longer quadratic, and rows outside the viewport skip layout and paint
|
|
1956
|
+
- (@krobipd) Corrected: the object browser stayed empty after closing the object customization dialog if any object was changed while the dialog was open (ioBroker/ioBroker.admin#3391)
|
|
1957
|
+
- (@krobipd) Changed: `ObjectBrowserClass.subscribes` and `.recordStates` are Sets instead of arrays now
|
|
1958
|
+
- (@krobipd) Improved: object browser performance on large installations — bursts of object changes cause one tree rebuild instead of several, state-change echoes no longer trigger redraws, subscription bookkeeping is no longer quadratic, and rows outside the viewport skip layout and paint
|
|
1914
1959
|
|
|
1915
1960
|
### 9.0.22 (2026-08-21)
|
|
1916
1961
|
- (@GermanBluefox) Corrected layout of Config view
|
|
@@ -90,8 +90,60 @@ export default class ConfigGeneric<Props extends ConfigGenericProps = ConfigGene
|
|
|
90
90
|
private reportedHidden;
|
|
91
91
|
private calculateTimeout;
|
|
92
92
|
private readonly AsyncFunction;
|
|
93
|
+
/** Resolved `dependsOnStates` of this element: alias => state ID */
|
|
94
|
+
protected stateAliases: Record<string, string> | null;
|
|
95
|
+
/** True if this element has subscribed on states, so it must unsubscribe if `dependsOnStates` disappears */
|
|
96
|
+
protected statesSubscribed: boolean;
|
|
93
97
|
constructor(props: Props);
|
|
94
98
|
protected getCachedObject(id: string): Promise<ioBroker.Object | null>;
|
|
99
|
+
/**
|
|
100
|
+
* Bring the `dependsOnStates` attribute into the form `{ alias: stateID }`.
|
|
101
|
+
* In the short array form the state ID is used as an alias too.
|
|
102
|
+
*
|
|
103
|
+
* @param dependsOnStates value of the `dependsOnStates` attribute
|
|
104
|
+
* @returns aliases with the still unresolved state IDs
|
|
105
|
+
*/
|
|
106
|
+
static normalizeDependsOnStates(dependsOnStates: Record<string, string> | string[] | undefined): Record<string, string>;
|
|
107
|
+
/**
|
|
108
|
+
* Build the real state ID out of a `dependsOnStates` entry: resolve the `${data.xxx}` patterns and
|
|
109
|
+
* expand a leading dot (`.info.running`) to the namespace of the own instance.
|
|
110
|
+
*
|
|
111
|
+
* @param id state ID from the schema
|
|
112
|
+
* @returns the resolved state ID or an empty string if it cannot be used
|
|
113
|
+
*/
|
|
114
|
+
protected resolveStateId(id: string): Promise<string>;
|
|
115
|
+
/**
|
|
116
|
+
* Resolve all state IDs of a `dependsOnStates` attribute.
|
|
117
|
+
*
|
|
118
|
+
* @param dependsOnStates value of the `dependsOnStates` attribute
|
|
119
|
+
* @returns aliases with the resolved state IDs
|
|
120
|
+
*/
|
|
121
|
+
protected resolveDependsOnStates(dependsOnStates: Record<string, string> | string[] | undefined): Promise<Record<string, string>>;
|
|
122
|
+
/**
|
|
123
|
+
* Subscribe on the given state IDs. The promise is resolved as soon as all values are known,
|
|
124
|
+
* so the following calculation does not run with unknown states.
|
|
125
|
+
*
|
|
126
|
+
* @param ids resolved state IDs
|
|
127
|
+
*/
|
|
128
|
+
protected subscribeOnStateIds(ids: string[]): Promise<void>;
|
|
129
|
+
/**
|
|
130
|
+
* Resolve the `dependsOnStates` attribute of this element and subscribe on the states.
|
|
131
|
+
*
|
|
132
|
+
* It is called before every calculation and not only once, because a state ID can contain a
|
|
133
|
+
* `${data.xxx}` pattern and so it can change if the user changes the configuration.
|
|
134
|
+
*/
|
|
135
|
+
protected updateStateSubscriptions(): Promise<void>;
|
|
136
|
+
/** True if this element depends on ioBroker states */
|
|
137
|
+
protected usesDependsOnStates(): boolean;
|
|
138
|
+
/** One of the states from `dependsOnStates` changed => recalculate `hidden`, `disabled`, ... */
|
|
139
|
+
protected onDependsOnStateChanged: () => void;
|
|
140
|
+
/**
|
|
141
|
+
* Values of the subscribed states for the JS functions and patterns (`_states.<alias>`).
|
|
142
|
+
*
|
|
143
|
+
* @param aliases alias => state ID. If omitted, the aliases of this element are used
|
|
144
|
+
* @returns alias => state object. `null` if the state does not exist
|
|
145
|
+
*/
|
|
146
|
+
protected getStateValues(aliases?: Record<string, string> | null): Record<string, ioBroker.State | null>;
|
|
95
147
|
componentDidMount(): Promise<void>;
|
|
96
148
|
private defaultSendTo;
|
|
97
149
|
componentWillUnmount(): void;
|
|
@@ -104,7 +156,9 @@ export default class ConfigGeneric<Props extends ConfigGenericProps = ConfigGene
|
|
|
104
156
|
getText(text: ioBroker.StringOrTranslated | undefined | null | {
|
|
105
157
|
/** @deprecated */
|
|
106
158
|
func: ioBroker.StringOrTranslated;
|
|
107
|
-
}, noTranslation?: boolean
|
|
159
|
+
}, noTranslation?: boolean,
|
|
160
|
+
/** Values for `_states`. If omitted, the states of this element are used */
|
|
161
|
+
states?: Record<string, ioBroker.State | null>): string;
|
|
108
162
|
getTextAsync(text: ioBroker.StringOrTranslated, noTranslation?: boolean): Promise<string>;
|
|
109
163
|
renderDialogConfirm(): JSX.Element | null;
|
|
110
164
|
getIcon(iconSettings?: ConfigIconType | null): JSX.Element | null;
|
|
@@ -147,8 +201,12 @@ export default class ConfigGeneric<Props extends ConfigGenericProps = ConfigGene
|
|
|
147
201
|
* @returns false if the element must not be shown on this host
|
|
148
202
|
*/
|
|
149
203
|
static isHostAllowed(schema: Pick<ConfigItem, 'os' | 'notOs' | 'docker'>, hostInfo?: JsonConfigHostInfo): boolean;
|
|
150
|
-
execute(func: string | boolean | Record<string, string> | undefined, defaultValue: string | number | boolean | undefined, data: Record<string, any> | undefined, arrayIndex: number | undefined, globalData: Record<string, any> | undefined, funcName?: string
|
|
151
|
-
|
|
204
|
+
execute(func: string | boolean | Record<string, string> | undefined, defaultValue: string | number | boolean | undefined, data: Record<string, any> | undefined, arrayIndex: number | undefined, globalData: Record<string, any> | undefined, funcName?: string,
|
|
205
|
+
/** Values for `_states`. If omitted, the states of this element are used */
|
|
206
|
+
states?: Record<string, ioBroker.State | null>): Promise<string | number | boolean | undefined>;
|
|
207
|
+
executeCustom(func: string | boolean | Record<string, string> | undefined, data: Record<string, any>, customObj: Record<string, any> | undefined, instanceObj: ioBroker.InstanceObject | undefined, arrayIndex: number | undefined, globalData: Record<string, any> | undefined, funcName?: string,
|
|
208
|
+
/** Values for `_states`. If omitted, the states of this element are used */
|
|
209
|
+
states?: Record<string, ioBroker.State | null>): Promise<string | boolean | number | null | undefined>;
|
|
152
210
|
calculate(schema: Record<string, any>): Promise<{
|
|
153
211
|
error: boolean;
|
|
154
212
|
disabled: boolean;
|
|
@@ -164,10 +222,14 @@ export default class ConfigGeneric<Props extends ConfigGenericProps = ConfigGene
|
|
|
164
222
|
getObject: (id: string) => Promise<ioBroker.Object | null>;
|
|
165
223
|
getPatternAsync(pattern: string | {
|
|
166
224
|
func: string;
|
|
167
|
-
}, data?: Record<string, any> | null, noTranslation?: boolean
|
|
225
|
+
}, data?: Record<string, any> | null, noTranslation?: boolean,
|
|
226
|
+
/** Values for `_states`. If omitted, the states of this element are used */
|
|
227
|
+
states?: Record<string, ioBroker.State | null>): Promise<string>;
|
|
168
228
|
getPattern(pattern: string | {
|
|
169
229
|
func: string;
|
|
170
|
-
}, data?: Record<string, any>, noTranslation?: boolean
|
|
230
|
+
}, data?: Record<string, any>, noTranslation?: boolean,
|
|
231
|
+
/** Values for `_states`. If omitted, the states of this element are used */
|
|
232
|
+
states?: Record<string, ioBroker.State | null>): string;
|
|
171
233
|
updateCalculatedValues(): void;
|
|
172
234
|
render(): string | JSX.Element | null;
|
|
173
235
|
}
|
|
@@ -65,6 +65,10 @@ export default class ConfigGeneric extends Component {
|
|
|
65
65
|
reportedHidden = false;
|
|
66
66
|
calculateTimeout = null;
|
|
67
67
|
AsyncFunction;
|
|
68
|
+
/** Resolved `dependsOnStates` of this element: alias => state ID */
|
|
69
|
+
stateAliases = null;
|
|
70
|
+
/** True if this element has subscribed on states, so it must unsubscribe if `dependsOnStates` disappears */
|
|
71
|
+
statesSubscribed = false;
|
|
68
72
|
constructor(props) {
|
|
69
73
|
super(props);
|
|
70
74
|
this.AsyncFunction = Object.getPrototypeOf(async function () { }).constructor;
|
|
@@ -98,7 +102,128 @@ export default class ConfigGeneric extends Component {
|
|
|
98
102
|
}
|
|
99
103
|
return (await this.props.oContext.socket.getObject(id)) || null;
|
|
100
104
|
}
|
|
105
|
+
/**
|
|
106
|
+
* Bring the `dependsOnStates` attribute into the form `{ alias: stateID }`.
|
|
107
|
+
* In the short array form the state ID is used as an alias too.
|
|
108
|
+
*
|
|
109
|
+
* @param dependsOnStates value of the `dependsOnStates` attribute
|
|
110
|
+
* @returns aliases with the still unresolved state IDs
|
|
111
|
+
*/
|
|
112
|
+
static normalizeDependsOnStates(dependsOnStates) {
|
|
113
|
+
const result = {};
|
|
114
|
+
if (!dependsOnStates) {
|
|
115
|
+
return result;
|
|
116
|
+
}
|
|
117
|
+
if (Array.isArray(dependsOnStates)) {
|
|
118
|
+
dependsOnStates.forEach(id => {
|
|
119
|
+
if (id && typeof id === 'string') {
|
|
120
|
+
result[id] = id;
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
else if (isObject(dependsOnStates)) {
|
|
125
|
+
Object.keys(dependsOnStates).forEach(alias => {
|
|
126
|
+
if (dependsOnStates[alias] && typeof dependsOnStates[alias] === 'string') {
|
|
127
|
+
result[alias] = dependsOnStates[alias];
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
console.error(`[JsonConfigComponent] Invalid dependsOnStates: ${JSON.stringify(dependsOnStates)}`);
|
|
133
|
+
}
|
|
134
|
+
return result;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Build the real state ID out of a `dependsOnStates` entry: resolve the `${data.xxx}` patterns and
|
|
138
|
+
* expand a leading dot (`.info.running`) to the namespace of the own instance.
|
|
139
|
+
*
|
|
140
|
+
* @param id state ID from the schema
|
|
141
|
+
* @returns the resolved state ID or an empty string if it cannot be used
|
|
142
|
+
*/
|
|
143
|
+
async resolveStateId(id) {
|
|
144
|
+
let result = (id || '').toString();
|
|
145
|
+
if (result.includes('${')) {
|
|
146
|
+
result = await this.getPatternAsync(result, null, true);
|
|
147
|
+
}
|
|
148
|
+
result = result.trim();
|
|
149
|
+
if (result.startsWith('.')) {
|
|
150
|
+
result = `${this.props.oContext.adapterName}.${this.props.oContext.instance || 0}${result}`;
|
|
151
|
+
}
|
|
152
|
+
if (result.includes('*')) {
|
|
153
|
+
console.error(`[JsonConfigComponent] Wildcards are not allowed in dependsOnStates: ${result}`);
|
|
154
|
+
return '';
|
|
155
|
+
}
|
|
156
|
+
return result;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Resolve all state IDs of a `dependsOnStates` attribute.
|
|
160
|
+
*
|
|
161
|
+
* @param dependsOnStates value of the `dependsOnStates` attribute
|
|
162
|
+
* @returns aliases with the resolved state IDs
|
|
163
|
+
*/
|
|
164
|
+
async resolveDependsOnStates(dependsOnStates) {
|
|
165
|
+
const aliases = ConfigGeneric.normalizeDependsOnStates(dependsOnStates);
|
|
166
|
+
const resolved = {};
|
|
167
|
+
for (const alias of Object.keys(aliases)) {
|
|
168
|
+
const id = await this.resolveStateId(aliases[alias]);
|
|
169
|
+
if (id) {
|
|
170
|
+
resolved[alias] = id;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return resolved;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Subscribe on the given state IDs. The promise is resolved as soon as all values are known,
|
|
177
|
+
* so the following calculation does not run with unknown states.
|
|
178
|
+
*
|
|
179
|
+
* @param ids resolved state IDs
|
|
180
|
+
*/
|
|
181
|
+
async subscribeOnStateIds(ids) {
|
|
182
|
+
if (!ids.length && !this.statesSubscribed) {
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
this.statesSubscribed = !!ids.length;
|
|
186
|
+
await this.props.oContext.subscribeStates?.(this, ids, this.onDependsOnStateChanged);
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Resolve the `dependsOnStates` attribute of this element and subscribe on the states.
|
|
190
|
+
*
|
|
191
|
+
* It is called before every calculation and not only once, because a state ID can contain a
|
|
192
|
+
* `${data.xxx}` pattern and so it can change if the user changes the configuration.
|
|
193
|
+
*/
|
|
194
|
+
async updateStateSubscriptions() {
|
|
195
|
+
this.stateAliases = await this.resolveDependsOnStates(this.props.schema?.dependsOnStates);
|
|
196
|
+
await this.subscribeOnStateIds(Object.values(this.stateAliases));
|
|
197
|
+
}
|
|
198
|
+
/** True if this element depends on ioBroker states */
|
|
199
|
+
usesDependsOnStates() {
|
|
200
|
+
return !!this.props.schema?.dependsOnStates || this.statesSubscribed;
|
|
201
|
+
}
|
|
202
|
+
/** One of the states from `dependsOnStates` changed => recalculate `hidden`, `disabled`, ... */
|
|
203
|
+
onDependsOnStateChanged = () => {
|
|
204
|
+
this.forceUpdate();
|
|
205
|
+
};
|
|
206
|
+
/**
|
|
207
|
+
* Values of the subscribed states for the JS functions and patterns (`_states.<alias>`).
|
|
208
|
+
*
|
|
209
|
+
* @param aliases alias => state ID. If omitted, the aliases of this element are used
|
|
210
|
+
* @returns alias => state object. `null` if the state does not exist
|
|
211
|
+
*/
|
|
212
|
+
getStateValues(aliases) {
|
|
213
|
+
const map = aliases === undefined ? this.stateAliases : aliases;
|
|
214
|
+
const result = {};
|
|
215
|
+
if (map) {
|
|
216
|
+
Object.keys(map).forEach(alias => {
|
|
217
|
+
result[alias] = this.props.oContext.getStateValue?.(map[alias]) ?? null;
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
return result;
|
|
221
|
+
}
|
|
101
222
|
async componentDidMount() {
|
|
223
|
+
if (this.usesDependsOnStates()) {
|
|
224
|
+
// Subscribe before the first calculation, so `defaultFunc` and `hidden` already see the values
|
|
225
|
+
await this.updateStateSubscriptions();
|
|
226
|
+
}
|
|
102
227
|
if (this.props.schema?.defaultFunc) {
|
|
103
228
|
if (this.props.custom) {
|
|
104
229
|
this.defaultValue = await this.executeCustom(this.props.schema.defaultFunc, this.props.data, this.props.customObj, this.props.oContext.instanceObj, this.props.arrayIndex, this.props.globalData, 'defaultFunc');
|
|
@@ -195,6 +320,10 @@ export default class ConfigGeneric extends Component {
|
|
|
195
320
|
if (this.props.attr) {
|
|
196
321
|
this.props.oContext.registerOnForceUpdate?.(this.props.attr);
|
|
197
322
|
}
|
|
323
|
+
// Not only if `statesSubscribed` is true: the element could have released its states before (an ID
|
|
324
|
+
// with a `${data.xxx}` pattern was resolved to nothing), but it is still registered in the pool
|
|
325
|
+
this.props.oContext.unsubscribeStates?.(this);
|
|
326
|
+
this.statesSubscribed = false;
|
|
198
327
|
if (this.sendToTimeout) {
|
|
199
328
|
clearTimeout(this.sendToTimeout);
|
|
200
329
|
this.sendToTimeout = null;
|
|
@@ -259,14 +388,16 @@ export default class ConfigGeneric extends Component {
|
|
|
259
388
|
ConfigGeneric.setValue(data[part], attr, value);
|
|
260
389
|
}
|
|
261
390
|
}
|
|
262
|
-
getText(text, noTranslation
|
|
391
|
+
getText(text, noTranslation,
|
|
392
|
+
/** Values for `_states`. If omitted, the states of this element are used */
|
|
393
|
+
states) {
|
|
263
394
|
if (!text) {
|
|
264
395
|
return '';
|
|
265
396
|
}
|
|
266
397
|
if (typeof text === 'string') {
|
|
267
398
|
const strText = noTranslation ? text : I18n.t(text);
|
|
268
399
|
if (strText.includes('${')) {
|
|
269
|
-
return this.getPattern(strText, undefined, noTranslation);
|
|
400
|
+
return this.getPattern(strText, undefined, noTranslation, states);
|
|
270
401
|
}
|
|
271
402
|
return strText;
|
|
272
403
|
}
|
|
@@ -277,9 +408,9 @@ export default class ConfigGeneric extends Component {
|
|
|
277
408
|
if (typeof funcText.func === 'object') {
|
|
278
409
|
return this.getPattern(funcText.func[this.lang] ||
|
|
279
410
|
funcText.func.en ||
|
|
280
|
-
'', undefined, true);
|
|
411
|
+
'', undefined, true, states);
|
|
281
412
|
}
|
|
282
|
-
return this.getPattern(funcText.func, undefined, noTranslation);
|
|
413
|
+
return this.getPattern(funcText.func, undefined, noTranslation, states);
|
|
283
414
|
}
|
|
284
415
|
return text[this.lang] || text.en || '';
|
|
285
416
|
}
|
|
@@ -604,7 +735,9 @@ export default class ConfigGeneric extends Component {
|
|
|
604
735
|
}
|
|
605
736
|
return true;
|
|
606
737
|
}
|
|
607
|
-
async execute(func, defaultValue, data, arrayIndex, globalData, funcName
|
|
738
|
+
async execute(func, defaultValue, data, arrayIndex, globalData, funcName,
|
|
739
|
+
/** Values for `_states`. If omitted, the states of this element are used */
|
|
740
|
+
states) {
|
|
608
741
|
let fun;
|
|
609
742
|
if (isObject(func)) {
|
|
610
743
|
fun = func.func;
|
|
@@ -623,8 +756,8 @@ export default class ConfigGeneric extends Component {
|
|
|
623
756
|
fun = ConfigGeneric.ensureAwaitGetObject(fun);
|
|
624
757
|
}
|
|
625
758
|
try {
|
|
626
|
-
const f = new this.AsyncFunction('data', 'originalData', '_system', '_alive', '_common', '_socket', '_instance', 'arrayIndex', 'globalData', '_changed', '_href', 'getObject', '_os', '_arch', '_host', fun.includes('return') ? fun : `return ${fun}`);
|
|
627
|
-
const result = await f(data || this.props.data || {}, this.props.originalData, this.props.oContext.systemConfig, this.props.alive, this.props.common, this.props.oContext.socket, this.props.oContext.instance, arrayIndex, globalData || {}, this.props.changed, window.location.href, this.getObject, this.props.oContext.hostInfo?.os || '', this.props.oContext.hostInfo?.arch || '', this.props.oContext.hostInfo || {});
|
|
759
|
+
const f = new this.AsyncFunction('data', 'originalData', '_system', '_alive', '_common', '_socket', '_instance', 'arrayIndex', 'globalData', '_changed', '_href', 'getObject', '_os', '_arch', '_host', '_states', fun.includes('return') ? fun : `return ${fun}`);
|
|
760
|
+
const result = await f(data || this.props.data || {}, this.props.originalData, this.props.oContext.systemConfig, this.props.alive, this.props.common, this.props.oContext.socket, this.props.oContext.instance, arrayIndex, globalData || {}, this.props.changed, window.location.href, this.getObject, this.props.oContext.hostInfo?.os || '', this.props.oContext.hostInfo?.arch || '', this.props.oContext.hostInfo || {}, states || this.getStateValues());
|
|
628
761
|
this.debugLog(funcName || 'JS function', fun, result, data);
|
|
629
762
|
return result;
|
|
630
763
|
}
|
|
@@ -634,7 +767,9 @@ export default class ConfigGeneric extends Component {
|
|
|
634
767
|
return defaultValue;
|
|
635
768
|
}
|
|
636
769
|
}
|
|
637
|
-
async executeCustom(func, data, customObj, instanceObj, arrayIndex, globalData, funcName
|
|
770
|
+
async executeCustom(func, data, customObj, instanceObj, arrayIndex, globalData, funcName,
|
|
771
|
+
/** Values for `_states`. If omitted, the states of this element are used */
|
|
772
|
+
states) {
|
|
638
773
|
let fun;
|
|
639
774
|
if (isObject(func)) {
|
|
640
775
|
fun = func.func;
|
|
@@ -653,8 +788,8 @@ export default class ConfigGeneric extends Component {
|
|
|
653
788
|
fun = ConfigGeneric.ensureAwaitGetObject(fun);
|
|
654
789
|
}
|
|
655
790
|
try {
|
|
656
|
-
const f = new this.AsyncFunction('data', 'originalData', '_system', 'instanceObj', 'customObj', '_socket', 'arrayIndex', 'globalData', '_changed', '_href', 'getObject', '_os', '_arch', '_host', fun.includes('return') ? fun : `return ${fun}`);
|
|
657
|
-
const result = await f(data || this.props.data, this.props.originalData, this.props.oContext.systemConfig, instanceObj || {}, customObj || {}, this.props.oContext.socket, arrayIndex, globalData || {}, this.props.changed, window.location.href, this.getObject, this.props.oContext.hostInfo?.os || '', this.props.oContext.hostInfo?.arch || '', this.props.oContext.hostInfo || {});
|
|
791
|
+
const f = new this.AsyncFunction('data', 'originalData', '_system', 'instanceObj', 'customObj', '_socket', 'arrayIndex', 'globalData', '_changed', '_href', 'getObject', '_os', '_arch', '_host', '_states', fun.includes('return') ? fun : `return ${fun}`);
|
|
792
|
+
const result = await f(data || this.props.data, this.props.originalData, this.props.oContext.systemConfig, instanceObj || {}, customObj || {}, this.props.oContext.socket, arrayIndex, globalData || {}, this.props.changed, window.location.href, this.getObject, this.props.oContext.hostInfo?.os || '', this.props.oContext.hostInfo?.arch || '', this.props.oContext.hostInfo || {}, states || this.getStateValues());
|
|
658
793
|
this.debugLog(funcName || 'JS function', fun, result, data);
|
|
659
794
|
return result;
|
|
660
795
|
}
|
|
@@ -801,7 +936,9 @@ export default class ConfigGeneric extends Component {
|
|
|
801
936
|
return null;
|
|
802
937
|
}
|
|
803
938
|
};
|
|
804
|
-
async getPatternAsync(pattern, data, noTranslation
|
|
939
|
+
async getPatternAsync(pattern, data, noTranslation,
|
|
940
|
+
/** Values for `_states`. If omitted, the states of this element are used */
|
|
941
|
+
states) {
|
|
805
942
|
data ||= this.props.data;
|
|
806
943
|
if (!pattern) {
|
|
807
944
|
return '';
|
|
@@ -825,15 +962,15 @@ export default class ConfigGeneric extends Component {
|
|
|
825
962
|
}
|
|
826
963
|
try {
|
|
827
964
|
if (this.props.custom) {
|
|
828
|
-
const f = new this.AsyncFunction('data', 'originalData', 'arrayIndex', 'globalData', '_system', 'instanceObj', 'customObj', '_socket', '_changed', '_href', 'getObject', '_os', '_arch', '_host', `return \`${ConfigGeneric.escapeString(patternStr, data)}\``);
|
|
829
|
-
const text = await f(data, this.props.originalData, this.props.arrayIndex, this.props.globalData, this.props.oContext.systemConfig, this.props.oContext.instanceObj, this.props.customObj, this.props.oContext.socket, this.props.changed, window.location.href, this.getObject, this.props.oContext.hostInfo?.os || '', this.props.oContext.hostInfo?.arch || '', this.props.oContext.hostInfo || {});
|
|
965
|
+
const f = new this.AsyncFunction('data', 'originalData', 'arrayIndex', 'globalData', '_system', 'instanceObj', 'customObj', '_socket', '_changed', '_href', 'getObject', '_os', '_arch', '_host', '_states', `return \`${ConfigGeneric.escapeString(patternStr, data)}\``);
|
|
966
|
+
const text = await f(data, this.props.originalData, this.props.arrayIndex, this.props.globalData, this.props.oContext.systemConfig, this.props.oContext.instanceObj, this.props.customObj, this.props.oContext.socket, this.props.changed, window.location.href, this.getObject, this.props.oContext.hostInfo?.os || '', this.props.oContext.hostInfo?.arch || '', this.props.oContext.hostInfo || {}, states || this.getStateValues());
|
|
830
967
|
if (noTranslation) {
|
|
831
968
|
return text;
|
|
832
969
|
}
|
|
833
970
|
return I18n.t(text);
|
|
834
971
|
}
|
|
835
|
-
const f = new this.AsyncFunction('data', 'originalData', 'arrayIndex', 'globalData', '_system', '_alive', '_common', '_socket', '_changed', '_href', 'getObject', '_os', '_arch', '_host', `return \`${ConfigGeneric.escapeString(patternStr, data)}\``);
|
|
836
|
-
const text = await f(data, this.props.originalData, this.props.arrayIndex, this.props.globalData, this.props.oContext.systemConfig, this.props.alive, this.props.common, this.props.oContext.socket, this.props.changed, window.location.href, this.getObject, this.props.oContext.hostInfo?.os || '', this.props.oContext.hostInfo?.arch || '', this.props.oContext.hostInfo || {});
|
|
972
|
+
const f = new this.AsyncFunction('data', 'originalData', 'arrayIndex', 'globalData', '_system', '_alive', '_common', '_socket', '_changed', '_href', 'getObject', '_os', '_arch', '_host', '_states', `return \`${ConfigGeneric.escapeString(patternStr, data)}\``);
|
|
973
|
+
const text = await f(data, this.props.originalData, this.props.arrayIndex, this.props.globalData, this.props.oContext.systemConfig, this.props.alive, this.props.common, this.props.oContext.socket, this.props.changed, window.location.href, this.getObject, this.props.oContext.hostInfo?.os || '', this.props.oContext.hostInfo?.arch || '', this.props.oContext.hostInfo || {}, states || this.getStateValues());
|
|
837
974
|
if (noTranslation) {
|
|
838
975
|
return text;
|
|
839
976
|
}
|
|
@@ -844,7 +981,9 @@ export default class ConfigGeneric extends Component {
|
|
|
844
981
|
return patternStr;
|
|
845
982
|
}
|
|
846
983
|
}
|
|
847
|
-
getPattern(pattern, data, noTranslation
|
|
984
|
+
getPattern(pattern, data, noTranslation,
|
|
985
|
+
/** Values for `_states`. If omitted, the states of this element are used */
|
|
986
|
+
states) {
|
|
848
987
|
data ||= this.props.data;
|
|
849
988
|
if (!pattern) {
|
|
850
989
|
return '';
|
|
@@ -868,15 +1007,15 @@ export default class ConfigGeneric extends Component {
|
|
|
868
1007
|
}
|
|
869
1008
|
try {
|
|
870
1009
|
if (this.props.custom) {
|
|
871
|
-
const f = new Function('data', 'originalData', 'arrayIndex', 'globalData', '_system', 'instanceObj', 'customObj', '_socket', '_changed', '_href', '_os', '_arch', '_host', `return \`${ConfigGeneric.escapeString(patternStr, data)}\``);
|
|
872
|
-
const text = f(data, this.props.originalData, this.props.arrayIndex, this.props.globalData, this.props.oContext.systemConfig, this.props.oContext.instanceObj, this.props.customObj, this.props.oContext.socket, this.props.changed, window.location.href, this.props.oContext.hostInfo?.os || '', this.props.oContext.hostInfo?.arch || '', this.props.oContext.hostInfo || {});
|
|
1010
|
+
const f = new Function('data', 'originalData', 'arrayIndex', 'globalData', '_system', 'instanceObj', 'customObj', '_socket', '_changed', '_href', '_os', '_arch', '_host', '_states', `return \`${ConfigGeneric.escapeString(patternStr, data)}\``);
|
|
1011
|
+
const text = f(data, this.props.originalData, this.props.arrayIndex, this.props.globalData, this.props.oContext.systemConfig, this.props.oContext.instanceObj, this.props.customObj, this.props.oContext.socket, this.props.changed, window.location.href, this.props.oContext.hostInfo?.os || '', this.props.oContext.hostInfo?.arch || '', this.props.oContext.hostInfo || {}, states || this.getStateValues());
|
|
873
1012
|
if (noTranslation) {
|
|
874
1013
|
return text;
|
|
875
1014
|
}
|
|
876
1015
|
return I18n.t(text);
|
|
877
1016
|
}
|
|
878
|
-
const f = new Function('data', 'originalData', 'arrayIndex', 'globalData', '_system', '_alive', '_common', '_socket', '_changed', '_href', '_os', '_arch', '_host', `return \`${ConfigGeneric.escapeString(patternStr, data)}\``);
|
|
879
|
-
const text = f(data, this.props.originalData, this.props.arrayIndex, this.props.globalData, this.props.oContext.systemConfig, this.props.alive, this.props.common, this.props.oContext.socket, this.props.changed, window.location.href, this.props.oContext.hostInfo?.os || '', this.props.oContext.hostInfo?.arch || '', this.props.oContext.hostInfo || {});
|
|
1017
|
+
const f = new Function('data', 'originalData', 'arrayIndex', 'globalData', '_system', '_alive', '_common', '_socket', '_changed', '_href', '_os', '_arch', '_host', '_states', `return \`${ConfigGeneric.escapeString(patternStr, data)}\``);
|
|
1018
|
+
const text = f(data, this.props.originalData, this.props.arrayIndex, this.props.globalData, this.props.oContext.systemConfig, this.props.alive, this.props.common, this.props.oContext.socket, this.props.changed, window.location.href, this.props.oContext.hostInfo?.os || '', this.props.oContext.hostInfo?.arch || '', this.props.oContext.hostInfo || {}, states || this.getStateValues());
|
|
880
1019
|
if (noTranslation) {
|
|
881
1020
|
return text;
|
|
882
1021
|
}
|
|
@@ -897,6 +1036,11 @@ export default class ConfigGeneric extends Component {
|
|
|
897
1036
|
if (!schema) {
|
|
898
1037
|
return;
|
|
899
1038
|
}
|
|
1039
|
+
if (this.usesDependsOnStates()) {
|
|
1040
|
+
// The state IDs can contain `${data.xxx}` patterns, so they must be resolved anew if the data changed.
|
|
1041
|
+
// It waits for the first values, so the element is not calculated with unknown states.
|
|
1042
|
+
await this.updateStateSubscriptions();
|
|
1043
|
+
}
|
|
900
1044
|
const { error, disabled, hidden, defaultValue } = await this.calculate(schema);
|
|
901
1045
|
if (!this.state.calculatedValues ||
|
|
902
1046
|
this.state.calculatedValues.error !== error ||
|