@iobroker/gui-components 10.0.10 → 10.0.12
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 +6 -2
- package/build/Components/ObjectBrowser/HistoryChart.d.ts +43 -0
- package/build/Components/ObjectBrowser/HistoryChart.js +205 -0
- package/build/Components/ObjectBrowser/HistoryChart.js.map +1 -0
- package/build/Components/ObjectBrowser/ObjectBrowserClass.d.ts +0 -8
- package/build/Components/ObjectBrowser/ObjectBrowserClass.js +9 -53
- package/build/Components/ObjectBrowser/ObjectBrowserClass.js.map +1 -1
- package/build/Components/ObjectBrowser/renderLeaf.js +1 -1
- package/build/Components/ObjectBrowser/renderLeaf.js.map +1 -1
- package/build/Components/ObjectBrowser/utils.d.ts +0 -1
- package/build/Components/ObjectBrowser/utils.js +0 -33
- package/build/Components/ObjectBrowser/utils.js.map +1 -1
- package/build/Components/Utils.js +2 -2
- package/build/Components/Utils.js.map +1 -1
- package/build/i18n/de.json +2 -0
- package/build/i18n/en.json +2 -0
- package/build/i18n/es.json +2 -0
- package/build/i18n/fr.json +2 -0
- package/build/i18n/it.json +2 -0
- package/build/i18n/nl.json +2 -0
- package/build/i18n/pl.json +2 -0
- package/build/i18n/pt.json +2 -0
- package/build/i18n/ru.json +2 -0
- package/build/i18n/uk.json +2 -0
- package/build/i18n/zh-cn.json +2 -0
- package/i18n/de.json +2 -0
- package/i18n/en.json +2 -0
- package/i18n/es.json +2 -0
- package/i18n/fr.json +2 -0
- package/i18n/it.json +2 -0
- package/i18n/nl.json +2 -0
- package/i18n/pl.json +2 -0
- package/i18n/pt.json +2 -0
- package/i18n/ru.json +2 -0
- package/i18n/uk.json +2 -0
- package/i18n/zh-cn.json +2 -0
- package/package.json +2 -2
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.0.
|
|
39
|
+
"@iobroker/gui-components": "^10.0.12",
|
|
40
40
|
"@mui/icons-material": "^9.0.1",
|
|
41
41
|
"@mui/material": "^9.0.1",
|
|
42
42
|
"react": "^19.2.5",
|
|
@@ -856,7 +856,11 @@ You can find the migration instructions:
|
|
|
856
856
|
-->
|
|
857
857
|
|
|
858
858
|
## Changelog
|
|
859
|
-
### 10.0.
|
|
859
|
+
### 10.0.12 (2026-08-02)
|
|
860
|
+
|
|
861
|
+
- (@GermanBluefox) Replaced the spark line
|
|
862
|
+
|
|
863
|
+
### 10.0.11 (2026-07-31)
|
|
860
864
|
|
|
861
865
|
- (@GermanBluefox) Added support of styled scrollbars
|
|
862
866
|
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright 2020-2026, Denis Haev <dogafox@gmail.com>
|
|
3
|
+
*
|
|
4
|
+
* MIT License
|
|
5
|
+
*
|
|
6
|
+
* The small 24 hours chart that is shown in the value tooltip of the object browser, if the state
|
|
7
|
+
* is recorded by a history adapter. It draws itself into an SVG, so no charting library is needed.
|
|
8
|
+
*/
|
|
9
|
+
import { type JSX } from 'react';
|
|
10
|
+
import type { Connection } from '../../Connection';
|
|
11
|
+
import type { Translate } from '../../types';
|
|
12
|
+
/** One point of the drawn line */
|
|
13
|
+
interface ChartPoint {
|
|
14
|
+
ts: number;
|
|
15
|
+
val: number;
|
|
16
|
+
}
|
|
17
|
+
interface HistoryChartProps {
|
|
18
|
+
socket: Connection;
|
|
19
|
+
/** ID of the state, the history of which is shown */
|
|
20
|
+
id: string;
|
|
21
|
+
/** Instance of the history adapter, e.g. `history.0` */
|
|
22
|
+
instance: string;
|
|
23
|
+
t: Translate;
|
|
24
|
+
/** Booleans are drawn as steps and are labeled with `false`/`true` instead of `0`/`1` */
|
|
25
|
+
isBoolean?: boolean;
|
|
26
|
+
/** Write the decimal point as a comma */
|
|
27
|
+
isFloatComma?: boolean;
|
|
28
|
+
/** Current value of the state, so the line reaches the right edge even if the last record is older */
|
|
29
|
+
current?: ChartPoint | null;
|
|
30
|
+
/** Shown period in hours */
|
|
31
|
+
hours?: number;
|
|
32
|
+
/**
|
|
33
|
+
* Color of the line. The tooltip has a dark background in the light theme too, so one color fits
|
|
34
|
+
* both themes.
|
|
35
|
+
*/
|
|
36
|
+
color?: string;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* The last 24 hours of a state as a sparkline: area, line, the min/max of the period and the value
|
|
40
|
+
* at the right edge. Reads the history itself as soon as it is mounted.
|
|
41
|
+
*/
|
|
42
|
+
export declare function HistoryChart(props: HistoryChartProps): JSX.Element;
|
|
43
|
+
export {};
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright 2020-2026, Denis Haev <dogafox@gmail.com>
|
|
3
|
+
*
|
|
4
|
+
* MIT License
|
|
5
|
+
*
|
|
6
|
+
* The small 24 hours chart that is shown in the value tooltip of the object browser, if the state
|
|
7
|
+
* is recorded by a history adapter. It draws itself into an SVG, so no charting library is needed.
|
|
8
|
+
*/
|
|
9
|
+
import React, { useEffect, useState } from 'react';
|
|
10
|
+
const WIDTH = 224;
|
|
11
|
+
const HEIGHT = 68;
|
|
12
|
+
/** Left border of the plot. Left of it are the min/max labels */
|
|
13
|
+
const PLOT_LEFT = 38;
|
|
14
|
+
const PLOT_RIGHT = 217;
|
|
15
|
+
const PLOT_TOP = 5;
|
|
16
|
+
const PLOT_BOTTOM = 54;
|
|
17
|
+
/** The values are mapped into this band, so the line does not touch the border of the plot */
|
|
18
|
+
const VALUE_TOP = 10;
|
|
19
|
+
const VALUE_BOTTOM = 50;
|
|
20
|
+
const FONT_SIZE = 9;
|
|
21
|
+
/** Number of the aggregated intervals that are requested from the history: one point every 15 minutes */
|
|
22
|
+
const INTERVALS = 96;
|
|
23
|
+
const DEFAULT_HOURS = 24;
|
|
24
|
+
/** Blue that stays readable on the dark tooltip background */
|
|
25
|
+
const DEFAULT_COLOR = '#4a93eb';
|
|
26
|
+
/** Every chart needs its own gradient, so the IDs must not collide */
|
|
27
|
+
let gradientCounter = 0;
|
|
28
|
+
/**
|
|
29
|
+
* Format a value for the min/max label: short, but without pretending a precision that is not there.
|
|
30
|
+
*/
|
|
31
|
+
function formatValue(value, isFloatComma) {
|
|
32
|
+
const abs = Math.abs(value);
|
|
33
|
+
let text;
|
|
34
|
+
if (abs >= 100_000) {
|
|
35
|
+
// the label may not be wider than the space left of the plot
|
|
36
|
+
text = `${Math.round(value / 1000)}k`;
|
|
37
|
+
}
|
|
38
|
+
else if (Number.isInteger(value)) {
|
|
39
|
+
text = value.toString();
|
|
40
|
+
}
|
|
41
|
+
else if (abs >= 10) {
|
|
42
|
+
text = value.toFixed(1);
|
|
43
|
+
}
|
|
44
|
+
else if (abs >= 1) {
|
|
45
|
+
text = value.toFixed(2);
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
text = value.toFixed(3);
|
|
49
|
+
}
|
|
50
|
+
if (text.includes('.')) {
|
|
51
|
+
text = text.replace(/0+$/, '').replace(/\.$/, '');
|
|
52
|
+
}
|
|
53
|
+
return isFloatComma ? text.replace('.', ',') : text;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Take the numbers out of the history result: booleans become 0/1, nulls and strings are ignored.
|
|
57
|
+
*/
|
|
58
|
+
function readPoints(values) {
|
|
59
|
+
const points = [];
|
|
60
|
+
for (const item of values || []) {
|
|
61
|
+
if (!item || typeof item.ts !== 'number' || item.val === null || item.val === undefined) {
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
const val = typeof item.val === 'boolean' ? (item.val ? 1 : 0) : Number(item.val);
|
|
65
|
+
if (Number.isFinite(val)) {
|
|
66
|
+
points.push({ ts: item.ts, val });
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
// The min/max aggregation delivers two points per interval, and not always in the right order
|
|
70
|
+
points.sort((a, b) => a.ts - b.ts);
|
|
71
|
+
return points;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Build the SVG path of the line. Booleans (and everything else that is drawn as steps) keep their
|
|
75
|
+
* value till the next point instead of ramping to it.
|
|
76
|
+
*/
|
|
77
|
+
function buildLine(points, x, y, steps) {
|
|
78
|
+
let path = '';
|
|
79
|
+
points.forEach((point, i) => {
|
|
80
|
+
const px = x(point.ts).toFixed(2);
|
|
81
|
+
const py = y(point.val).toFixed(2);
|
|
82
|
+
if (!i) {
|
|
83
|
+
path = `M${px} ${py}`;
|
|
84
|
+
}
|
|
85
|
+
else if (steps) {
|
|
86
|
+
path += `H${px}V${py}`;
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
path += `L${px} ${py}`;
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
return path;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* The last 24 hours of a state as a sparkline: area, line, the min/max of the period and the value
|
|
96
|
+
* at the right edge. Reads the history itself as soon as it is mounted.
|
|
97
|
+
*/
|
|
98
|
+
export function HistoryChart(props) {
|
|
99
|
+
const { socket, id, instance, t, isBoolean, isFloatComma, current, hours = DEFAULT_HOURS } = props;
|
|
100
|
+
const color = props.color || DEFAULT_COLOR;
|
|
101
|
+
const [gradientId] = useState(() => `iob-history-chart-${++gradientCounter}`);
|
|
102
|
+
const [data, setData] = useState(null);
|
|
103
|
+
const [failed, setFailed] = useState(false);
|
|
104
|
+
useEffect(() => {
|
|
105
|
+
let mounted = true;
|
|
106
|
+
const to = Date.now();
|
|
107
|
+
const from = to - hours * 3_600_000;
|
|
108
|
+
socket
|
|
109
|
+
.getHistory(id, {
|
|
110
|
+
instance,
|
|
111
|
+
start: from,
|
|
112
|
+
end: to,
|
|
113
|
+
step: Math.round((to - from) / INTERVALS),
|
|
114
|
+
from: false,
|
|
115
|
+
ack: false,
|
|
116
|
+
q: false,
|
|
117
|
+
addId: false,
|
|
118
|
+
aggregate: 'minmax',
|
|
119
|
+
})
|
|
120
|
+
.then(values => {
|
|
121
|
+
if (mounted) {
|
|
122
|
+
setData({ points: readPoints(values), from, to });
|
|
123
|
+
}
|
|
124
|
+
})
|
|
125
|
+
.catch(e => {
|
|
126
|
+
console.warn(`Cannot read history of ${id}: ${e}`);
|
|
127
|
+
if (mounted) {
|
|
128
|
+
setFailed(true);
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
return () => {
|
|
132
|
+
mounted = false;
|
|
133
|
+
};
|
|
134
|
+
}, [socket, id, instance, hours]);
|
|
135
|
+
let content;
|
|
136
|
+
if (failed || (data && !data.points.length)) {
|
|
137
|
+
content = (React.createElement("text", { x: WIDTH / 2, y: (PLOT_TOP + PLOT_BOTTOM) / 2 + 3, textAnchor: "middle", fill: "currentColor", fillOpacity: 0.5, fontSize: 11 }, t('ra_No data')));
|
|
138
|
+
}
|
|
139
|
+
else if (!data) {
|
|
140
|
+
content = (React.createElement("line", { x1: PLOT_LEFT + 6, x2: PLOT_RIGHT - 6, y1: (PLOT_TOP + PLOT_BOTTOM) / 2, y2: (PLOT_TOP + PLOT_BOTTOM) / 2, stroke: "currentColor", strokeOpacity: 0.25, strokeWidth: 1.5, strokeDasharray: "3 4", strokeLinecap: "round" },
|
|
141
|
+
React.createElement("animate", { attributeName: "stroke-opacity", values: "0.3;0.1;0.3", dur: "1.4s", repeatCount: "indefinite" })));
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
const points = data.points.slice();
|
|
145
|
+
// Let the line reach "now": a state that was not written since an hour still has its value
|
|
146
|
+
if (current && Number.isFinite(current.val) && (!points.length || current.ts >= points[points.length - 1].ts)) {
|
|
147
|
+
points.push({ ts: Math.min(current.ts, data.to), val: current.val });
|
|
148
|
+
}
|
|
149
|
+
let min = points[0].val;
|
|
150
|
+
let max = points[0].val;
|
|
151
|
+
for (const point of points) {
|
|
152
|
+
if (point.val < min) {
|
|
153
|
+
min = point.val;
|
|
154
|
+
}
|
|
155
|
+
if (point.val > max) {
|
|
156
|
+
max = point.val;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
// The drawn range is a bit larger than the values, so the line has some air above and below
|
|
160
|
+
let low;
|
|
161
|
+
let high;
|
|
162
|
+
if (isBoolean) {
|
|
163
|
+
low = 0;
|
|
164
|
+
high = 1;
|
|
165
|
+
}
|
|
166
|
+
else if (min === max) {
|
|
167
|
+
// a constant value is drawn in the middle
|
|
168
|
+
low = min - 1;
|
|
169
|
+
high = max + 1;
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
const air = (max - min) * 0.1;
|
|
173
|
+
low = min - air;
|
|
174
|
+
high = max + air;
|
|
175
|
+
}
|
|
176
|
+
const scaleX = (PLOT_RIGHT - PLOT_LEFT) / (data.to - data.from || 1);
|
|
177
|
+
const x = (ts) => Math.min(PLOT_RIGHT, Math.max(PLOT_LEFT, PLOT_LEFT + (ts - data.from) * scaleX));
|
|
178
|
+
const y = (val) => VALUE_BOTTOM - ((val - low) / (high - low)) * (VALUE_BOTTOM - VALUE_TOP);
|
|
179
|
+
const steps = !!isBoolean;
|
|
180
|
+
const line = buildLine(points, x, y, steps);
|
|
181
|
+
const lastX = x(points[points.length - 1].ts);
|
|
182
|
+
const lastY = y(points[points.length - 1].val);
|
|
183
|
+
// the area is closed against the bottom of the plot, and not against the lowest value
|
|
184
|
+
const area = `${line}V${PLOT_BOTTOM}H${x(points[0].ts).toFixed(2)}Z`;
|
|
185
|
+
const zeroY = low < 0 && high > 0 ? y(0) : null;
|
|
186
|
+
content = (React.createElement(React.Fragment, null,
|
|
187
|
+
React.createElement("path", { d: area, fill: `url(#${gradientId})` }),
|
|
188
|
+
zeroY !== null ? (React.createElement("line", { x1: PLOT_LEFT, x2: PLOT_RIGHT, y1: zeroY, y2: zeroY, stroke: "currentColor", strokeOpacity: 0.25, strokeWidth: 1, strokeDasharray: "2 3" })) : null,
|
|
189
|
+
React.createElement("path", { d: line, fill: "none", stroke: color, strokeWidth: 1.6, strokeLinecap: "round", strokeLinejoin: "round" }),
|
|
190
|
+
React.createElement("circle", { cx: lastX, cy: lastY, r: 5, fill: color, fillOpacity: 0.25 }),
|
|
191
|
+
React.createElement("circle", { cx: lastX, cy: lastY, r: 2.4, fill: color }),
|
|
192
|
+
React.createElement("text", { x: PLOT_LEFT - 5, y: y(isBoolean ? 1 : max) + 3, textAnchor: "end", fill: "currentColor", fillOpacity: 0.55, fontSize: FONT_SIZE }, isBoolean ? 'true' : formatValue(max, isFloatComma)),
|
|
193
|
+
isBoolean || min !== max ? (React.createElement("text", { x: PLOT_LEFT - 5, y: y(isBoolean ? 0 : min) + 3, textAnchor: "end", fill: "currentColor", fillOpacity: 0.55, fontSize: FONT_SIZE }, isBoolean ? 'false' : formatValue(min, isFloatComma))) : null));
|
|
194
|
+
}
|
|
195
|
+
return (React.createElement("svg", { viewBox: `0 0 ${WIDTH} ${HEIGHT}`, style: { width: '100%', height: 'auto', display: 'block', marginTop: 4 } },
|
|
196
|
+
React.createElement("defs", null,
|
|
197
|
+
React.createElement("linearGradient", { id: gradientId, x1: "0", y1: "0", x2: "0", y2: "1" },
|
|
198
|
+
React.createElement("stop", { offset: "0%", stopColor: color, stopOpacity: 0.35 }),
|
|
199
|
+
React.createElement("stop", { offset: "100%", stopColor: color, stopOpacity: 0 }))),
|
|
200
|
+
React.createElement("rect", { x: PLOT_LEFT, y: PLOT_TOP, width: PLOT_RIGHT - PLOT_LEFT, height: PLOT_BOTTOM - PLOT_TOP, rx: 4, fill: "currentColor", fillOpacity: 0.07 }),
|
|
201
|
+
content,
|
|
202
|
+
React.createElement("text", { x: PLOT_LEFT, y: HEIGHT - 3, fill: "currentColor", fillOpacity: 0.45, fontSize: FONT_SIZE }, `-${hours} h`),
|
|
203
|
+
React.createElement("text", { x: PLOT_RIGHT, y: HEIGHT - 3, textAnchor: "end", fill: "currentColor", fillOpacity: 0.45, fontSize: FONT_SIZE }, t('ra_now'))));
|
|
204
|
+
}
|
|
205
|
+
//# sourceMappingURL=HistoryChart.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"HistoryChart.js","sourceRoot":"./src/","sources":["Components/ObjectBrowser/HistoryChart.tsx"],"names":[],"mappings":"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","sourcesContent":["/**\n * Copyright 2020-2026, Denis Haev <dogafox@gmail.com>\n *\n * MIT License\n *\n * The small 24 hours chart that is shown in the value tooltip of the object browser, if the state\n * is recorded by a history adapter. It draws itself into an SVG, so no charting library is needed.\n */\nimport React, { type JSX, useEffect, useState } from 'react';\n\nimport type { Connection } from '../../Connection';\nimport type { Translate } from '../../types';\n\n/** One point of the drawn line */\ninterface ChartPoint {\n ts: number;\n val: number;\n}\n\ninterface HistoryChartProps {\n socket: Connection;\n /** ID of the state, the history of which is shown */\n id: string;\n /** Instance of the history adapter, e.g. `history.0` */\n instance: string;\n t: Translate;\n /** Booleans are drawn as steps and are labeled with `false`/`true` instead of `0`/`1` */\n isBoolean?: boolean;\n /** Write the decimal point as a comma */\n isFloatComma?: boolean;\n /** Current value of the state, so the line reaches the right edge even if the last record is older */\n current?: ChartPoint | null;\n /** Shown period in hours */\n hours?: number;\n /**\n * Color of the line. The tooltip has a dark background in the light theme too, so one color fits\n * both themes.\n */\n color?: string;\n}\n\nconst WIDTH = 224;\nconst HEIGHT = 68;\n/** Left border of the plot. Left of it are the min/max labels */\nconst PLOT_LEFT = 38;\nconst PLOT_RIGHT = 217;\nconst PLOT_TOP = 5;\nconst PLOT_BOTTOM = 54;\n/** The values are mapped into this band, so the line does not touch the border of the plot */\nconst VALUE_TOP = 10;\nconst VALUE_BOTTOM = 50;\nconst FONT_SIZE = 9;\n/** Number of the aggregated intervals that are requested from the history: one point every 15 minutes */\nconst INTERVALS = 96;\nconst DEFAULT_HOURS = 24;\n/** Blue that stays readable on the dark tooltip background */\nconst DEFAULT_COLOR = '#4a93eb';\n\n/** Every chart needs its own gradient, so the IDs must not collide */\nlet gradientCounter = 0;\n\n/**\n * Format a value for the min/max label: short, but without pretending a precision that is not there.\n */\nfunction formatValue(value: number, isFloatComma?: boolean): string {\n const abs = Math.abs(value);\n let text: string;\n if (abs >= 100_000) {\n // the label may not be wider than the space left of the plot\n text = `${Math.round(value / 1000)}k`;\n } else if (Number.isInteger(value)) {\n text = value.toString();\n } else if (abs >= 10) {\n text = value.toFixed(1);\n } else if (abs >= 1) {\n text = value.toFixed(2);\n } else {\n text = value.toFixed(3);\n }\n if (text.includes('.')) {\n text = text.replace(/0+$/, '').replace(/\\.$/, '');\n }\n return isFloatComma ? text.replace('.', ',') : text;\n}\n\n/**\n * Take the numbers out of the history result: booleans become 0/1, nulls and strings are ignored.\n */\nfunction readPoints(values: ioBroker.GetHistoryResult): ChartPoint[] {\n const points: ChartPoint[] = [];\n\n for (const item of values || []) {\n if (!item || typeof item.ts !== 'number' || item.val === null || item.val === undefined) {\n continue;\n }\n const val = typeof item.val === 'boolean' ? (item.val ? 1 : 0) : Number(item.val);\n if (Number.isFinite(val)) {\n points.push({ ts: item.ts, val });\n }\n }\n\n // The min/max aggregation delivers two points per interval, and not always in the right order\n points.sort((a, b) => a.ts - b.ts);\n\n return points;\n}\n\n/**\n * Build the SVG path of the line. Booleans (and everything else that is drawn as steps) keep their\n * value till the next point instead of ramping to it.\n */\nfunction buildLine(\n points: ChartPoint[],\n x: (ts: number) => number,\n y: (val: number) => number,\n steps: boolean,\n): string {\n let path = '';\n\n points.forEach((point, i) => {\n const px = x(point.ts).toFixed(2);\n const py = y(point.val).toFixed(2);\n if (!i) {\n path = `M${px} ${py}`;\n } else if (steps) {\n path += `H${px}V${py}`;\n } else {\n path += `L${px} ${py}`;\n }\n });\n\n return path;\n}\n\n/**\n * The last 24 hours of a state as a sparkline: area, line, the min/max of the period and the value\n * at the right edge. Reads the history itself as soon as it is mounted.\n */\nexport function HistoryChart(props: HistoryChartProps): JSX.Element {\n const { socket, id, instance, t, isBoolean, isFloatComma, current, hours = DEFAULT_HOURS } = props;\n const color = props.color || DEFAULT_COLOR;\n const [gradientId] = useState(() => `iob-history-chart-${++gradientCounter}`);\n const [data, setData] = useState<{ points: ChartPoint[]; from: number; to: number } | null>(null);\n const [failed, setFailed] = useState(false);\n\n useEffect(() => {\n let mounted = true;\n const to = Date.now();\n const from = to - hours * 3_600_000;\n\n socket\n .getHistory(id, {\n instance,\n start: from,\n end: to,\n step: Math.round((to - from) / INTERVALS),\n from: false,\n ack: false,\n q: false,\n addId: false,\n aggregate: 'minmax',\n })\n .then(values => {\n if (mounted) {\n setData({ points: readPoints(values), from, to });\n }\n })\n .catch(e => {\n console.warn(`Cannot read history of ${id}: ${e}`);\n if (mounted) {\n setFailed(true);\n }\n });\n\n return () => {\n mounted = false;\n };\n }, [socket, id, instance, hours]);\n\n let content: JSX.Element;\n\n if (failed || (data && !data.points.length)) {\n content = (\n <text\n x={WIDTH / 2}\n y={(PLOT_TOP + PLOT_BOTTOM) / 2 + 3}\n textAnchor=\"middle\"\n fill=\"currentColor\"\n fillOpacity={0.5}\n fontSize={11}\n >\n {t('ra_No data')}\n </text>\n );\n } else if (!data) {\n content = (\n <line\n x1={PLOT_LEFT + 6}\n x2={PLOT_RIGHT - 6}\n y1={(PLOT_TOP + PLOT_BOTTOM) / 2}\n y2={(PLOT_TOP + PLOT_BOTTOM) / 2}\n stroke=\"currentColor\"\n strokeOpacity={0.25}\n strokeWidth={1.5}\n strokeDasharray=\"3 4\"\n strokeLinecap=\"round\"\n >\n <animate\n attributeName=\"stroke-opacity\"\n values=\"0.3;0.1;0.3\"\n dur=\"1.4s\"\n repeatCount=\"indefinite\"\n />\n </line>\n );\n } else {\n const points = data.points.slice();\n // Let the line reach \"now\": a state that was not written since an hour still has its value\n if (current && Number.isFinite(current.val) && (!points.length || current.ts >= points[points.length - 1].ts)) {\n points.push({ ts: Math.min(current.ts, data.to), val: current.val });\n }\n\n let min = points[0].val;\n let max = points[0].val;\n for (const point of points) {\n if (point.val < min) {\n min = point.val;\n }\n if (point.val > max) {\n max = point.val;\n }\n }\n\n // The drawn range is a bit larger than the values, so the line has some air above and below\n let low: number;\n let high: number;\n if (isBoolean) {\n low = 0;\n high = 1;\n } else if (min === max) {\n // a constant value is drawn in the middle\n low = min - 1;\n high = max + 1;\n } else {\n const air = (max - min) * 0.1;\n low = min - air;\n high = max + air;\n }\n\n const scaleX = (PLOT_RIGHT - PLOT_LEFT) / (data.to - data.from || 1);\n const x = (ts: number): number =>\n Math.min(PLOT_RIGHT, Math.max(PLOT_LEFT, PLOT_LEFT + (ts - data.from) * scaleX));\n const y = (val: number): number => VALUE_BOTTOM - ((val - low) / (high - low)) * (VALUE_BOTTOM - VALUE_TOP);\n\n const steps = !!isBoolean;\n const line = buildLine(points, x, y, steps);\n const lastX = x(points[points.length - 1].ts);\n const lastY = y(points[points.length - 1].val);\n // the area is closed against the bottom of the plot, and not against the lowest value\n const area = `${line}V${PLOT_BOTTOM}H${x(points[0].ts).toFixed(2)}Z`;\n const zeroY = low < 0 && high > 0 ? y(0) : null;\n\n content = (\n <>\n <path\n d={area}\n fill={`url(#${gradientId})`}\n />\n {zeroY !== null ? (\n <line\n x1={PLOT_LEFT}\n x2={PLOT_RIGHT}\n y1={zeroY}\n y2={zeroY}\n stroke=\"currentColor\"\n strokeOpacity={0.25}\n strokeWidth={1}\n strokeDasharray=\"2 3\"\n />\n ) : null}\n <path\n d={line}\n fill=\"none\"\n stroke={color}\n strokeWidth={1.6}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n <circle\n cx={lastX}\n cy={lastY}\n r={5}\n fill={color}\n fillOpacity={0.25}\n />\n <circle\n cx={lastX}\n cy={lastY}\n r={2.4}\n fill={color}\n />\n <text\n x={PLOT_LEFT - 5}\n y={y(isBoolean ? 1 : max) + 3}\n textAnchor=\"end\"\n fill=\"currentColor\"\n fillOpacity={0.55}\n fontSize={FONT_SIZE}\n >\n {isBoolean ? 'true' : formatValue(max, isFloatComma)}\n </text>\n {isBoolean || min !== max ? (\n <text\n x={PLOT_LEFT - 5}\n y={y(isBoolean ? 0 : min) + 3}\n textAnchor=\"end\"\n fill=\"currentColor\"\n fillOpacity={0.55}\n fontSize={FONT_SIZE}\n >\n {isBoolean ? 'false' : formatValue(min, isFloatComma)}\n </text>\n ) : null}\n </>\n );\n }\n\n return (\n <svg\n viewBox={`0 0 ${WIDTH} ${HEIGHT}`}\n style={{ width: '100%', height: 'auto', display: 'block', marginTop: 4 }}\n >\n <defs>\n <linearGradient\n id={gradientId}\n x1=\"0\"\n y1=\"0\"\n x2=\"0\"\n y2=\"1\"\n >\n <stop\n offset=\"0%\"\n stopColor={color}\n stopOpacity={0.35}\n />\n <stop\n offset=\"100%\"\n stopColor={color}\n stopOpacity={0}\n />\n </linearGradient>\n </defs>\n <rect\n x={PLOT_LEFT}\n y={PLOT_TOP}\n width={PLOT_RIGHT - PLOT_LEFT}\n height={PLOT_BOTTOM - PLOT_TOP}\n rx={4}\n fill=\"currentColor\"\n fillOpacity={0.07}\n />\n {content}\n <text\n x={PLOT_LEFT}\n y={HEIGHT - 3}\n fill=\"currentColor\"\n fillOpacity={0.45}\n fontSize={FONT_SIZE}\n >\n {`-${hours} h`}\n </text>\n <text\n x={PLOT_RIGHT}\n y={HEIGHT - 3}\n textAnchor=\"end\"\n fill=\"currentColor\"\n fillOpacity={0.45}\n fontSize={FONT_SIZE}\n >\n {t('ra_now')}\n </text>\n </svg>\n );\n}\n"]}
|
|
@@ -13,13 +13,6 @@ import { getSelectIdIconFromObjects } from './utils';
|
|
|
13
13
|
import type { Width } from '../../types';
|
|
14
14
|
import type { ObjectBrowserProps, AdapterColumn, ObjectBrowserFilter, ObjectBrowserPossibleColumns, ObjectBrowserState, TreeInfo, TreeItem, CustomAdminColumnStored, ObjectEvent } from './types';
|
|
15
15
|
export { getSelectIdIconFromObjects, type ObjectBrowserFilter };
|
|
16
|
-
declare global {
|
|
17
|
-
interface Window {
|
|
18
|
-
sparkline: {
|
|
19
|
-
sparkline: (el: HTMLDivElement, data: number[]) => JSX.Element;
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
16
|
declare module '@mui/material/Button' {
|
|
24
17
|
interface ButtonPropsColorOverrides {
|
|
25
18
|
grey: true;
|
|
@@ -255,7 +248,6 @@ export declare class ObjectBrowserClass extends Component<ObjectBrowserProps, Ob
|
|
|
255
248
|
onCopy(e: React.MouseEvent, text: string | undefined): void;
|
|
256
249
|
renderTooltipAccessControl(acl: ioBroker.StateACL): null | JSX.Element;
|
|
257
250
|
renderColumnButtons(id: string, item: TreeItem): (JSX.Element | null)[] | JSX.Element | null;
|
|
258
|
-
readHistory(id: string): void;
|
|
259
251
|
getTooltipInfo(id: string, cb?: () => void): void;
|
|
260
252
|
private _syncEnum;
|
|
261
253
|
syncEnum(id: string, enumName: 'func' | 'room', newArray: string[]): Promise<void>;
|
|
@@ -17,7 +17,8 @@ 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,
|
|
20
|
+
import { applyFilter, binarySearch, buildTree, findNode, formatValue, generateFile, getName, colVar, widthFromContainer, growVar, getSelectIdIconFromObjects, setCustomValue, } from './utils';
|
|
21
|
+
import { HistoryChart } from './HistoryChart';
|
|
21
22
|
import { styles } from './styles';
|
|
22
23
|
import * as dialogs from './dialogs';
|
|
23
24
|
import * as toolbar from './toolbar';
|
|
@@ -1602,57 +1603,6 @@ export class ObjectBrowserClass extends Component {
|
|
|
1602
1603
|
renderColumnButtons(id, item) {
|
|
1603
1604
|
return leaf.renderColumnButtons(this, id, item);
|
|
1604
1605
|
}
|
|
1605
|
-
readHistory(id) {
|
|
1606
|
-
/* interface GetHistoryOptions {
|
|
1607
|
-
instance?: string;
|
|
1608
|
-
start?: number;
|
|
1609
|
-
end?: number;
|
|
1610
|
-
step?: number;
|
|
1611
|
-
count?: number;
|
|
1612
|
-
from?: boolean;
|
|
1613
|
-
ack?: boolean;
|
|
1614
|
-
q?: boolean;
|
|
1615
|
-
addID?: boolean;
|
|
1616
|
-
limit?: number;
|
|
1617
|
-
ignoreNull?: boolean;
|
|
1618
|
-
sessionId?: any;
|
|
1619
|
-
aggregate?: 'minmax' | 'min' | 'max' | 'average' | 'total' | 'count' | 'none';
|
|
1620
|
-
} */
|
|
1621
|
-
if (window.sparkline &&
|
|
1622
|
-
this.defaultHistory &&
|
|
1623
|
-
this.objects[id]?.common?.custom &&
|
|
1624
|
-
this.objects[id].common.custom[this.defaultHistory]) {
|
|
1625
|
-
const now = new Date();
|
|
1626
|
-
now.setHours(now.getHours() - 24);
|
|
1627
|
-
now.setMinutes(0);
|
|
1628
|
-
now.setSeconds(0);
|
|
1629
|
-
now.setMilliseconds(0);
|
|
1630
|
-
const nowMs = now.getTime();
|
|
1631
|
-
this.props.socket
|
|
1632
|
-
.getHistory(id, {
|
|
1633
|
-
instance: this.defaultHistory,
|
|
1634
|
-
start: nowMs,
|
|
1635
|
-
end: Date.now(),
|
|
1636
|
-
step: 3600000,
|
|
1637
|
-
from: false,
|
|
1638
|
-
ack: false,
|
|
1639
|
-
q: false,
|
|
1640
|
-
addId: false,
|
|
1641
|
-
aggregate: 'minmax',
|
|
1642
|
-
})
|
|
1643
|
-
.then(values => {
|
|
1644
|
-
const sparks = window.document.getElementsByClassName('sparkline');
|
|
1645
|
-
for (let s = 0; s < sparks.length; s++) {
|
|
1646
|
-
if (sparks[s].dataset.id === id) {
|
|
1647
|
-
const v = prepareSparkData(values, nowMs);
|
|
1648
|
-
window.sparkline.sparkline(sparks[s], v);
|
|
1649
|
-
break;
|
|
1650
|
-
}
|
|
1651
|
-
}
|
|
1652
|
-
})
|
|
1653
|
-
.catch(e => console.warn(`Cannot read history: ${e}`));
|
|
1654
|
-
}
|
|
1655
|
-
}
|
|
1656
1606
|
getTooltipInfo(id, cb) {
|
|
1657
1607
|
const obj = this.objects[id];
|
|
1658
1608
|
const state = this.states[id];
|
|
@@ -1697,7 +1647,13 @@ export class ObjectBrowserClass extends Component {
|
|
|
1697
1647
|
valFullRx.unshift(React.createElement("div", { key: "ctrl", style: { textDecoration: 'underline', fontWeight: 'bold' } }, this.texts.ctrlForLink));
|
|
1698
1648
|
}
|
|
1699
1649
|
else if (this.defaultHistory && obj?.common?.custom?.[this.defaultHistory]) {
|
|
1700
|
-
|
|
1650
|
+
const isNumber = typeof state?.val === 'number';
|
|
1651
|
+
const isBoolean = obj.common.type === 'boolean' || typeof state?.val === 'boolean';
|
|
1652
|
+
valFullRx.push(React.createElement(HistoryChart, { key: "chart", socket: this.props.socket, id: id, instance: this.defaultHistory, t: this.props.t, isBoolean: isBoolean, isFloatComma: this.props.isFloatComma === undefined
|
|
1653
|
+
? (this.systemConfig?.common.isFloatComma ?? true)
|
|
1654
|
+
: this.props.isFloatComma, current: isNumber || isBoolean
|
|
1655
|
+
? { ts: state.ts, val: typeof state.val === 'boolean' ? +state.val : state.val }
|
|
1656
|
+
: null }));
|
|
1701
1657
|
}
|
|
1702
1658
|
this.setState({ tooltipInfo: { el: valFullRx, id } }, () => cb && cb());
|
|
1703
1659
|
}
|