Skip to content

Commit 498a693

Browse files
committed
Corrected universal widget
1 parent 24a5074 commit 498a693

8 files changed

Lines changed: 68 additions & 28 deletions

File tree

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

admin/index_m.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
/>
2323
<script type="text/javascript" onerror="setTimeout(function(){window.location.reload()}, 5000)" src="./lib/js/socket.io.js"></script>
2424
<title>Devices</title>
25-
<script type="module" crossorigin src="./assets/index-Dg-FYP8O.js"></script>
25+
<script type="module" crossorigin src="./assets/index-Dj8M_5f0.js"></script>
2626
<link rel="modulepreload" crossorigin href="./assets/preload-helper-BDBacUwf.js">
2727
<link rel="modulepreload" crossorigin href="./assets/virtual_mf-REMOTE_ENTRY_ID_iobroker_devices__remoteEntry_js-D5_qLLYC.js">
2828
<link rel="stylesheet" crossorigin href="./assets/index-pCeIEsc7.css">

admin/tab.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
/>
2323
<script type="text/javascript" onerror="setTimeout(function(){window.location.reload()}, 5000)" src="./lib/js/socket.io.js"></script>
2424
<title>Devices</title>
25-
<script type="module" crossorigin src="./assets/index-Dg-FYP8O.js"></script>
25+
<script type="module" crossorigin src="./assets/index-Dj8M_5f0.js"></script>
2626
<link rel="modulepreload" crossorigin href="./assets/preload-helper-BDBacUwf.js">
2727
<link rel="modulepreload" crossorigin href="./assets/virtual_mf-REMOTE_ENTRY_ID_iobroker_devices__remoteEntry_js-D5_qLLYC.js">
2828
<link rel="stylesheet" crossorigin href="./assets/index-pCeIEsc7.css">

src-admin/src/WidgetsManager/Widgets/Universal.tsx

Lines changed: 59 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,14 @@ export class WidgetUniversal extends WidgetGeneric<WidgetUniversalState, WidgetU
348348
private opacityHandler: StateChangeListener | null = null;
349349
private iconHandlers: (StateChangeListener | null)[] = [];
350350

351+
/**
352+
* Type used to convert the primary raw value. Kept in an instance field (not state) so the
353+
* value handler reads it synchronously, independent of React's async setState.
354+
*/
355+
private primaryCommonType: ioBroker.CommonType = 'mixed';
356+
/** Last raw value received for the primary state, so it can be re-derived once the type is known */
357+
private lastPrimaryRaw: ioBroker.StateValue | null | undefined = undefined;
358+
351359
/** Cached object metadata keyed by state ID */
352360
private objectCache = new Map<string, ioBroker.StateObject>();
353361

@@ -587,16 +595,15 @@ export class WidgetUniversal extends WidgetGeneric<WidgetUniversalState, WidgetU
587595

588596
// Primary value
589597
if (this.props.settings.stateId) {
598+
const stateId = this.props.settings.stateId;
599+
// Reset per-subscription state (e.g. when the stateId changed)
600+
this.primaryCommonType = 'mixed';
601+
this.lastPrimaryRaw = undefined;
602+
590603
this.primaryHandler = (_id, state) => {
591604
const val = state?.val;
592-
let value: number | string | boolean | null;
593-
if (this.state.commonType === 'boolean') {
594-
value = val === true || val === 'true' || val === 1 || val === '1' || val === 'ON' || val === 'AN';
595-
} else if (this.state.commonType === 'number') {
596-
value = val != null ? Number(val) : null;
597-
} else {
598-
value = (val || '').toString();
599-
}
605+
this.lastPrimaryRaw = val;
606+
const value = this.convertPrimaryValue(val);
600607
this.setState({ value });
601608
// Capture immediately on every state change when QuickChart is on,
602609
// so the chart populates without waiting for the next interval tick.
@@ -608,18 +615,40 @@ export class WidgetUniversal extends WidgetGeneric<WidgetUniversalState, WidgetU
608615
this.captureQuickSample(value);
609616
}
610617
};
611-
ctx.getState(this.props.settings.stateId, this.primaryHandler);
612-
void this.getCachedObject(this.props.settings.stateId).then(obj => {
613-
if (obj?.common?.unit) {
614-
this.setState({
615-
unit: obj.common.unit,
616-
commonType: obj.common.type,
617-
commonName: this.getText(obj.common.name),
618-
});
619-
} else if (obj?.common?.type) {
620-
this.setState({ commonType: obj.common.type, commonName: this.getText(obj.common.name) });
618+
619+
// Option 2: resolve the object (→ commonType) FIRST, then subscribe to the value, so the
620+
// very first value is already converted with the correct type instead of being stringified raw.
621+
void (async () => {
622+
try {
623+
const obj = await this.getCachedObject(stateId);
624+
if (obj?.common?.type) {
625+
this.primaryCommonType = obj.common.type;
626+
if (obj.common.unit) {
627+
this.setState({
628+
unit: obj.common.unit,
629+
commonType: obj.common.type,
630+
commonName: this.getText(obj.common.name),
631+
});
632+
} else {
633+
this.setState({
634+
commonType: obj.common.type,
635+
commonName: this.getText(obj.common.name),
636+
});
637+
}
638+
// Option 3: a value may already have arrived before the type was known —
639+
// re-derive it now with the correct type so it gets formatted properly.
640+
if (this.lastPrimaryRaw !== undefined) {
641+
this.setState({ value: this.convertPrimaryValue(this.lastPrimaryRaw) });
642+
}
643+
}
644+
} catch {
645+
// Object not found / fetch failed — fall back to 'mixed' handling
621646
}
622-
});
647+
// Subscribe only after we attempted to resolve the type (guard against a fast unmount)
648+
if (this.primaryHandler) {
649+
ctx.getState(stateId, this.primaryHandler);
650+
}
651+
})();
623652
}
624653

625654
// Secondary value
@@ -807,6 +836,17 @@ export class WidgetUniversal extends WidgetGeneric<WidgetUniversalState, WidgetU
807836
return undefined;
808837
}
809838

839+
/** Convert a raw state value into the typed value used for display, based on the resolved common type. */
840+
private convertPrimaryValue(val: ioBroker.StateValue | null | undefined): number | string | boolean | null {
841+
if (this.primaryCommonType === 'boolean') {
842+
return val === true || val === 'true' || val === 1 || val === '1' || val === 'ON' || val === 'AN';
843+
}
844+
if (this.primaryCommonType === 'number') {
845+
return val != null ? Number(val) : null;
846+
}
847+
return (val ?? '').toString();
848+
}
849+
810850
private formatValue(val: number | boolean | string, hasIcon: boolean): string | null {
811851
if (typeof val === 'boolean') {
812852
if (val) {
Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

www/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
<script type="text/javascript" src="_socket/info.js"></script>
2222
<script type="text/javascript" onerror="setTimeout(function(){window.location.reload()}, 5000)" src="./lib/js/socket.io.js"></script>
2323
<title>Devices</title>
24-
<script type="module" crossorigin src="./assets/index-BV8eJ6ml.js"></script>
24+
<script type="module" crossorigin src="./assets/index-BMYs4oA2.js"></script>
2525
<link rel="stylesheet" crossorigin href="./assets/index-CmwOqvIb.css">
2626
</head>
2727
<body>

0 commit comments

Comments
 (0)