@devicechain/widgets 0.23.0 → 0.24.0-rc.1

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 (97) hide show
  1. package/README.md +8 -3
  2. package/dist/aggregate-fold.d.ts +3 -2
  3. package/dist/aggregate-strings.d.ts +1 -0
  4. package/dist/alarm-strings.d.ts +18 -0
  5. package/dist/binding-policy.d.ts +4 -0
  6. package/dist/bound-devices-notice.d.ts +30 -0
  7. package/dist/builtin-descriptors.d.ts +290 -249
  8. package/dist/builtin-option-docs.d.ts +2 -0
  9. package/dist/builtin-registry.d.ts +249 -226
  10. package/dist/category-geometry.d.ts +1 -0
  11. package/dist/chunks/{aggregate-feed-BD3CHNFU.js → aggregate-feed-4FSBQYJL.js} +39 -11
  12. package/dist/chunks/aggregate-feed-4FSBQYJL.js.map +7 -0
  13. package/dist/chunks/{aggregate-widgets-HZQI5HTX.js → aggregate-widgets-2ZQBW3YF.js} +376 -182
  14. package/dist/chunks/aggregate-widgets-2ZQBW3YF.js.map +7 -0
  15. package/dist/chunks/alarm-widgets-X4PX5MYJ.js +72 -0
  16. package/dist/chunks/alarm-widgets-X4PX5MYJ.js.map +7 -0
  17. package/dist/chunks/chunk-546TK2QG.js +722 -0
  18. package/dist/chunks/chunk-546TK2QG.js.map +7 -0
  19. package/dist/chunks/{chunk-WBKHYZU2.js → chunk-62CHYCHI.js} +6 -62
  20. package/dist/chunks/chunk-62CHYCHI.js.map +7 -0
  21. package/dist/chunks/chunk-BMFQOTYG.js +41 -0
  22. package/dist/chunks/chunk-BMFQOTYG.js.map +7 -0
  23. package/dist/chunks/{chunk-2D7JNKFE.js → chunk-JFH4QMLA.js} +28 -4
  24. package/dist/chunks/chunk-JFH4QMLA.js.map +7 -0
  25. package/dist/chunks/chunk-QMGKB6J3.js +84 -0
  26. package/dist/chunks/chunk-QMGKB6J3.js.map +7 -0
  27. package/dist/chunks/chunk-XNEBAY4J.js +19 -0
  28. package/dist/chunks/chunk-XNEBAY4J.js.map +7 -0
  29. package/dist/chunks/chunk-YYJV54BV.js +58 -0
  30. package/dist/chunks/chunk-YYJV54BV.js.map +7 -0
  31. package/dist/chunks/compare-feed-AY3CVKUZ.js +346 -0
  32. package/dist/chunks/compare-feed-AY3CVKUZ.js.map +7 -0
  33. package/dist/chunks/{control-widgets-3V2LSSRF.js → control-widgets-UCTJJJ2D.js} +267 -78
  34. package/dist/chunks/control-widgets-UCTJJJ2D.js.map +7 -0
  35. package/dist/chunks/event-markers-feed-GEGTIHXT.js +373 -0
  36. package/dist/chunks/event-markers-feed-GEGTIHXT.js.map +7 -0
  37. package/dist/chunks/{fleet-feed-5QGDLOV5.js → fleet-feed-2SKDR6ZQ.js} +47 -8
  38. package/dist/chunks/fleet-feed-2SKDR6ZQ.js.map +7 -0
  39. package/dist/chunks/presence-widgets-2LIUKN4Y.js +350 -0
  40. package/dist/chunks/presence-widgets-2LIUKN4Y.js.map +7 -0
  41. package/dist/command-control-strings.d.ts +5 -0
  42. package/dist/compare-feed.d.ts +13 -0
  43. package/dist/compare-model.d.ts +38 -0
  44. package/dist/compare-strings.d.ts +13 -0
  45. package/dist/documented-descriptors.d.ts +4826 -0
  46. package/dist/event-markers-feed.d.ts +11 -0
  47. package/dist/event-markers-model.d.ts +38 -0
  48. package/dist/event-markers-strings.d.ts +20 -0
  49. package/dist/event-markers-types.d.ts +7 -0
  50. package/dist/fleet-count-model.d.ts +13 -0
  51. package/dist/fleet-feed.d.ts +1 -0
  52. package/dist/fleet-model.d.ts +1 -0
  53. package/dist/fleet-strings.d.ts +11 -0
  54. package/dist/index.d.ts +6 -3
  55. package/dist/index.js +946 -398
  56. package/dist/index.js.map +4 -4
  57. package/dist/presence-feed.d.ts +24 -0
  58. package/dist/presence-model.d.ts +13 -0
  59. package/dist/presence-strings.d.ts +19 -0
  60. package/dist/sparkline.d.ts +23 -0
  61. package/dist/use-bound-devices.d.ts +26 -0
  62. package/dist/widgets/aggregate-lazy.d.ts +81 -46
  63. package/dist/widgets/aggregate-widgets.d.ts +1 -0
  64. package/dist/widgets/alarm-grouped.d.ts +17 -0
  65. package/dist/widgets/alarm-lazy.d.ts +63 -16
  66. package/dist/widgets/alarm-severity.d.ts +8 -0
  67. package/dist/widgets/alarm-table.d.ts +2 -0
  68. package/dist/widgets/alarm-type-tabs.d.ts +10 -0
  69. package/dist/widgets/alarm-widgets.d.ts +2 -0
  70. package/dist/widgets/command-stepper.d.ts +22 -0
  71. package/dist/widgets/control-lazy.d.ts +16 -24
  72. package/dist/widgets/device-status.d.ts +4 -0
  73. package/dist/widgets/fleet-count.d.ts +2 -0
  74. package/dist/widgets/gauge.d.ts +2 -15
  75. package/dist/widgets/image.d.ts +2 -6
  76. package/dist/widgets/label.d.ts +2 -6
  77. package/dist/widgets/latest-card.d.ts +2 -13
  78. package/dist/widgets/lazy-chunk.d.ts +7 -2
  79. package/dist/widgets/level-lazy.d.ts +14 -96
  80. package/dist/widgets/map.d.ts +8 -5
  81. package/dist/widgets/online-doughnut.d.ts +2 -0
  82. package/dist/widgets/presence-lazy.d.ts +27 -0
  83. package/dist/widgets/presence-widgets.d.ts +3 -0
  84. package/dist/widgets/selection-lazy.d.ts +2 -4
  85. package/dist/widgets/table-lazy.d.ts +2 -7
  86. package/dist/widgets/time-series-chart.d.ts +62 -12
  87. package/package.json +13 -13
  88. package/dist/chunks/aggregate-feed-BD3CHNFU.js.map +0 -7
  89. package/dist/chunks/aggregate-widgets-HZQI5HTX.js.map +0 -7
  90. package/dist/chunks/alarm-widgets-7UY6MIYZ.js +0 -9
  91. package/dist/chunks/alarm-widgets-7UY6MIYZ.js.map +0 -7
  92. package/dist/chunks/chunk-2D7JNKFE.js.map +0 -7
  93. package/dist/chunks/chunk-6OGI4UFJ.js +0 -367
  94. package/dist/chunks/chunk-6OGI4UFJ.js.map +0 -7
  95. package/dist/chunks/chunk-WBKHYZU2.js.map +0 -7
  96. package/dist/chunks/control-widgets-3V2LSSRF.js.map +0 -7
  97. package/dist/chunks/fleet-feed-5QGDLOV5.js.map +0 -7
@@ -0,0 +1,58 @@
1
+ // src/category-geometry.ts
2
+ function niceDomain(values, intervals = 4) {
3
+ const finite = values.filter(Number.isFinite);
4
+ let lo = Math.min(0, ...finite);
5
+ let hi = Math.max(0, ...finite);
6
+ if (lo === hi) hi = lo + 1;
7
+ const raw = (hi - lo) / Math.max(1, intervals);
8
+ const magnitude = 10 ** Math.floor(Math.log10(raw));
9
+ const norm = raw / magnitude;
10
+ const step = (norm <= 1 ? 1 : norm <= 2 ? 2 : norm <= 2.5 ? 2.5 : norm <= 5 ? 5 : 10) * magnitude;
11
+ lo = Math.floor(lo / step + 1e-9) * step;
12
+ hi = Math.ceil(hi / step - 1e-9) * step;
13
+ const decimals = Math.max(0, -Math.floor(Math.log10(step)) + 1);
14
+ const round = (v) => Number(v.toFixed(decimals));
15
+ const ticks = [];
16
+ for (let i = 0; lo + i * step <= hi + step * 1e-9; i++) ticks.push(round(lo + i * step));
17
+ return { min: round(lo), max: round(hi), step, ticks };
18
+ }
19
+ function pieSlices(values) {
20
+ const positive = values.map((v) => v !== null && v > 0 ? v : 0);
21
+ const total = positive.reduce((a, v) => a + v, 0);
22
+ let at = 0;
23
+ return positive.map((v, index) => {
24
+ const fraction = total > 0 ? v / total : 0;
25
+ const slice = { index, fraction, from: at, to: at + fraction };
26
+ at += fraction;
27
+ return slice;
28
+ });
29
+ }
30
+ function polar(cx, cy, r, turns) {
31
+ const angle = turns * 2 * Math.PI;
32
+ return { x: cx + r * Math.sin(angle), y: cy - r * Math.cos(angle) };
33
+ }
34
+ function arcPath(cx, cy, r, from, to) {
35
+ const span = to - from;
36
+ if (span <= 0 || span >= 1 - 1e-9) return void 0;
37
+ const a = polar(cx, cy, r, from);
38
+ const b = polar(cx, cy, r, to);
39
+ return `M${a.x},${a.y}A${r},${r} 0 ${span > 0.5 ? 1 : 0} 1 ${b.x},${b.y}`;
40
+ }
41
+ function wedgePath(cx, cy, r, from, to) {
42
+ const span = to - from;
43
+ if (span <= 0) return void 0;
44
+ if (span >= 1 - 1e-9) {
45
+ return `M${cx},${cy - r}A${r},${r} 0 1 1 ${cx},${cy + r}A${r},${r} 0 1 1 ${cx},${cy - r}Z`;
46
+ }
47
+ const a = polar(cx, cy, r, from);
48
+ const b = polar(cx, cy, r, to);
49
+ return `M${cx},${cy}L${a.x},${a.y}A${r},${r} 0 ${span > 0.5 ? 1 : 0} 1 ${b.x},${b.y}Z`;
50
+ }
51
+
52
+ export {
53
+ niceDomain,
54
+ pieSlices,
55
+ arcPath,
56
+ wedgePath
57
+ };
58
+ //# sourceMappingURL=chunk-YYJV54BV.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/category-geometry.ts"],
4
+ "sourcesContent": ["// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// The arithmetic of the bar and pie charts, apart from the drawing: a value axis that rounds out\n// to tidy ticks, and the angles of a pie's slices. Pure, so it is tested without a DOM.\n\nexport interface NiceDomain {\n min: number;\n max: number;\n step: number;\n ticks: number[];\n}\n\n// niceDomain returns an axis that holds every value and zero (a bar starts at zero, so the axis\n// always includes it), rounded out to a 1, 2, 2.5 or 5 step so the ticks read as round numbers. All\n// zeros give 0 to 1, so an empty chart still has an axis. Non-finite values are ignored.\nexport function niceDomain(values: readonly number[], intervals = 4): NiceDomain {\n const finite = values.filter(Number.isFinite);\n let lo = Math.min(0, ...finite);\n let hi = Math.max(0, ...finite);\n if (lo === hi) hi = lo + 1;\n const raw = (hi - lo) / Math.max(1, intervals);\n const magnitude = 10 ** Math.floor(Math.log10(raw));\n const norm = raw / magnitude;\n const step = (norm <= 1 ? 1 : norm <= 2 ? 2 : norm <= 2.5 ? 2.5 : norm <= 5 ? 5 : 10) * magnitude;\n lo = Math.floor(lo / step + 1e-9) * step;\n hi = Math.ceil(hi / step - 1e-9) * step;\n // Rounded to the step's own decimals so 0.1 + 0.2 does not become a tick label.\n const decimals = Math.max(0, -Math.floor(Math.log10(step)) + 1);\n const round = (v: number) => Number(v.toFixed(decimals));\n const ticks: number[] = [];\n for (let i = 0; lo + i * step <= hi + step * 1e-9; i++) ticks.push(round(lo + i * step));\n return { min: round(lo), max: round(hi), step, ticks };\n}\n\nexport interface Slice {\n index: number;\n // The share of the whole, 0 to 1.\n fraction: number;\n // Clockwise from twelve o'clock, in turns (0 to 1).\n from: number;\n to: number;\n}\n\n// pieSlices lays the values out clockwise from the top, in the order given. A value that is null,\n// zero or negative takes no angle (it keeps its place in the list with a fraction of 0), so the\n// angles of the rest are shares of the sum of the positive ones.\nexport function pieSlices(values: readonly (number | null)[]): Slice[] {\n const positive = values.map((v) => (v !== null && v > 0 ? v : 0));\n const total = positive.reduce((a, v) => a + v, 0);\n let at = 0;\n return positive.map((v, index) => {\n const fraction = total > 0 ? v / total : 0;\n const slice = { index, fraction, from: at, to: at + fraction };\n at += fraction;\n return slice;\n });\n}\n\n// The point on a circle of radius r about (cx, cy), `turns` clockwise from twelve o'clock.\nexport function polar(cx: number, cy: number, r: number, turns: number): { x: number; y: number } {\n const angle = turns * 2 * Math.PI;\n return { x: cx + r * Math.sin(angle), y: cy - r * Math.cos(angle) };\n}\n\n// The SVG path of an open arc about (cx, cy) at radius r, for a ring segment drawn as a stroke. A\n// span that fills the circle has no path here (an arc from a point to itself draws nothing): the\n// caller draws a circle, as wedgePath does for the pie. An empty span has none either.\nexport function arcPath(cx: number, cy: number, r: number, from: number, to: number): string | undefined {\n const span = to - from;\n if (span <= 0 || span >= 1 - 1e-9) return undefined;\n const a = polar(cx, cy, r, from);\n const b = polar(cx, cy, r, to);\n return `M${a.x},${a.y}A${r},${r} 0 ${span > 0.5 ? 1 : 0} 1 ${b.x},${b.y}`;\n}\n\n// The SVG path of one wedge. A slice that fills the circle is a circle (an arc from a point to\n// itself draws nothing), and an empty one has no path at all.\nexport function wedgePath(cx: number, cy: number, r: number, from: number, to: number): string | undefined {\n const span = to - from;\n if (span <= 0) return undefined;\n if (span >= 1 - 1e-9) {\n return `M${cx},${cy - r}A${r},${r} 0 1 1 ${cx},${cy + r}A${r},${r} 0 1 1 ${cx},${cy - r}Z`;\n }\n const a = polar(cx, cy, r, from);\n const b = polar(cx, cy, r, to);\n return `M${cx},${cy}L${a.x},${a.y}A${r},${r} 0 ${span > 0.5 ? 1 : 0} 1 ${b.x},${b.y}Z`;\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,346 @@
1
+ import {
2
+ measurementSpan
3
+ } from "./chunk-JFH4QMLA.js";
4
+ import {
5
+ seedWindow
6
+ } from "./chunk-62CHYCHI.js";
7
+ import {
8
+ fleetStrings
9
+ } from "./chunk-QMGKB6J3.js";
10
+ import "./chunk-BRH66T5F.js";
11
+ import {
12
+ LevelEmpty
13
+ } from "./chunk-XQQQNTBX.js";
14
+
15
+ // src/compare-feed.tsx
16
+ import {
17
+ DEFAULT_TIME_RANGE_SECONDS,
18
+ spanLabel
19
+ } from "@devicechain/dashboards";
20
+ import {
21
+ css,
22
+ fontVar,
23
+ optNumber,
24
+ optString,
25
+ optThresholds,
26
+ resolveStaleAfterSeconds,
27
+ spaceVar,
28
+ useWidgetScale,
29
+ useWidgetTimeRange,
30
+ WidgetFrame,
31
+ WidgetFreshnessProvider
32
+ } from "@devicechain/widget-sdk";
33
+ import { buildLineOption, EChart, useChartTheme } from "@devicechain/widget-sdk/echarts";
34
+ import { useEffect, useMemo, useReducer, useRef, useState } from "react";
35
+
36
+ // src/compare-model.ts
37
+ var DEFAULT_MAX_SERIES = 5;
38
+ var MAX_SERIES_LIMIT = 12;
39
+ var LIVE_PER_MEMBER = 600;
40
+ function seriesLimit(value) {
41
+ if (value === void 0 || !Number.isInteger(value) || value < 1) return DEFAULT_MAX_SERIES;
42
+ return Math.min(value, MAX_SERIES_LIMIT);
43
+ }
44
+ function capMembers(roster, limit) {
45
+ const read = roster.members.length;
46
+ const total = Math.max(roster.total, read);
47
+ const shown = roster.members.slice(0, limit);
48
+ return { shown, total, capped: shown.length < total, pagedBySource: total > read, read };
49
+ }
50
+ function memberLabels(members) {
51
+ const base = members.map((m) => m.name?.trim() || m.token);
52
+ const count = /* @__PURE__ */ new Map();
53
+ for (const b of base) count.set(b, (count.get(b) ?? 0) + 1);
54
+ const labels = /* @__PURE__ */ new Map();
55
+ members.forEach((m, i) => labels.set(m.token, (count.get(base[i]) ?? 0) > 1 && base[i] !== m.token ? `${base[i]} (${m.token})` : base[i]));
56
+ return labels;
57
+ }
58
+ function bucketSamples(token, measurement, buckets) {
59
+ return buckets.filter((b) => b.name === measurement && b.avg != null).map((b) => ({
60
+ id: `cmp-${token}-${b.name}-${b.bucketStart}`,
61
+ deviceToken: token,
62
+ eventType: 0,
63
+ occurredTime: b.bucketStart,
64
+ name: b.name,
65
+ value: b.avg,
66
+ classifier: null
67
+ })).sort((a, b) => Date.parse(a.occurredTime) - Date.parse(b.occurredTime));
68
+ }
69
+ function pushLive(buffer, sample) {
70
+ if (buffer.some((s) => s.id === sample.id)) return;
71
+ buffer.push(sample);
72
+ if (buffer.length > LIVE_PER_MEMBER) buffer.splice(0, buffer.length - LIVE_PER_MEMBER);
73
+ }
74
+ function composeSeries(i) {
75
+ const samples = [];
76
+ let historyFailed = 0;
77
+ let newest;
78
+ for (const m of i.shown) {
79
+ const label = i.labels.get(m.token) ?? m.token;
80
+ const h = i.history.get(m.token);
81
+ if (h === "failed") historyFailed += 1;
82
+ const stored = h && h !== "failed" ? h.samples : [];
83
+ const after = h && h !== "failed" ? h.end : Number.NEGATIVE_INFINITY;
84
+ const live = (i.live.get(m.token) ?? []).filter((s) => s.name === i.measurement && s.value != null && s.occurredTime != null && Date.parse(s.occurredTime) > after);
85
+ const points = [...stored, ...live].filter((s) => {
86
+ const t = Date.parse(s.occurredTime);
87
+ return t >= i.since && t <= i.until;
88
+ }).sort((a, b) => Date.parse(a.occurredTime) - Date.parse(b.occurredTime));
89
+ for (const s of points) {
90
+ samples.push({ ...s, name: label });
91
+ const t = Date.parse(s.occurredTime);
92
+ if (newest === void 0 || t > newest) newest = t;
93
+ }
94
+ }
95
+ return { samples, historyFailed, newest };
96
+ }
97
+
98
+ // src/compare-strings.ts
99
+ import { viewerLocale } from "@devicechain/widget-sdk";
100
+ var COMPARE_STRINGS = {
101
+ en: {
102
+ capped: (shown, total) => `Showing ${shown} of ${total} devices (the first ${shown}); raise Most lines, up to 12, to see more.`,
103
+ cappedPage: (shown, read, total) => `Showing ${shown} of ${total} devices: the group is larger than the ${read} devices read, and the first ${shown} are drawn.`,
104
+ bucketed: (bucket) => `avg per ${bucket}`,
105
+ bucketedTitle: (bucket) => `Stored history is drawn as the average of each ${bucket} bucket, one point per bucket; readings that arrived live since are drawn as they came, unaveraged.`,
106
+ thresholdNote: "Thresholds are reference lines; a plotted history point is a bucket average, not a reading.",
107
+ historyFailed: (n) => n === 1 ? "1 device\u2019s history could not be read; its line starts from live readings." : `${n} devices\u2019 history could not be read; their lines start from live readings.`,
108
+ cappedShort: (shown, total) => `${shown} of ${total} devices`,
109
+ thresholdShort: "thresholds are references",
110
+ historyFailedShort: (n) => `${n} unread`
111
+ },
112
+ es: {
113
+ capped: (shown, total) => `Mostrando ${shown} de ${total} dispositivos (los primeros ${shown}); aumente M\xE1ximo de l\xEDneas, hasta 12, para ver m\xE1s.`,
114
+ cappedPage: (shown, read, total) => `Mostrando ${shown} de ${total} dispositivos: el grupo es mayor que los ${read} dispositivos le\xEDdos, y se dibujan los primeros ${shown}.`,
115
+ bucketed: (bucket) => `prom. por ${bucket}`,
116
+ bucketedTitle: (bucket) => `El historial almacenado se dibuja como el promedio de cada intervalo de ${bucket}, un punto por intervalo; las lecturas llegadas en vivo desde entonces se dibujan tal cual, sin promediar.`,
117
+ thresholdNote: "Los umbrales son l\xEDneas de referencia; un punto de historial es un promedio por intervalo, no una lectura.",
118
+ historyFailed: (n) => n === 1 ? "No se pudo leer el historial de 1 dispositivo; su l\xEDnea parte de lecturas en vivo." : `No se pudo leer el historial de ${n} dispositivos; sus l\xEDneas parten de lecturas en vivo.`,
119
+ cappedShort: (shown, total) => `${shown} de ${total} dispositivos`,
120
+ thresholdShort: "umbrales son referencias",
121
+ historyFailedShort: (n) => `${n} sin leer`
122
+ }
123
+ };
124
+ function compareStrings(locale = viewerLocale()) {
125
+ return locale?.toLowerCase().split("-")[0] === "es" ? COMPARE_STRINGS.es : COMPARE_STRINGS.en;
126
+ }
127
+
128
+ // src/compare-feed.tsx
129
+ import { jsx, jsxs } from "react/jsx-runtime";
130
+ var FLUSH_MS = 250;
131
+ var MINUTE_MS = 6e4;
132
+ var RESEED_SETTLE_MS = 2e3;
133
+ var AGE_OUT_MS = 3e4;
134
+ var COMPACT_WIDTH = 320;
135
+ var COMPACT_HEIGHT = 200;
136
+ var emptyStore = (key) => ({ key, roster: null, live: /* @__PURE__ */ new Map(), history: /* @__PURE__ */ new Map(), forbidden: false, error: null });
137
+ function CompareFeed({ hub, widget, descriptor, ErrorFrame }) {
138
+ const range = useWidgetTimeRange();
139
+ const now = useMemo(() => hub.now ? () => hub.now() : Date.now, [hub]);
140
+ const spanSeconds = measurementSpan(descriptor, widget, range?.spanSeconds) ?? range?.spanSeconds ?? DEFAULT_TIME_RANGE_SECONDS;
141
+ const pausedAt = range?.paused ? range.pausedAt : void 0;
142
+ const measurement = optString(widget.options, "measurement") ?? widget.datasource?.measurements[0];
143
+ const limit = seriesLimit(optNumber(widget.options, "maxSeries"));
144
+ const ds = widget.datasource && measurement ? { ...widget.datasource, measurements: [measurement] } : void 0;
145
+ const dsKey = JSON.stringify(ds ?? null);
146
+ const supported = hub.subscribeMembers !== void 0 && hub.readMemberSeeds !== void 0;
147
+ const store = useRef(emptyStore(dsKey));
148
+ const [version, bump] = useReducer((n) => n + 1, 0);
149
+ const [rosterKey, setRosterKey] = useState(null);
150
+ const generation = useRef(0);
151
+ const flushTimer = useRef(void 0);
152
+ const boundaryTimer = useRef(void 0);
153
+ const [resumes, resumed] = useReducer((n) => n + 1, 0);
154
+ const drawn = useRef(/* @__PURE__ */ new Set());
155
+ const schedule = () => {
156
+ if (flushTimer.current !== void 0) return;
157
+ flushTimer.current = setTimeout(() => {
158
+ flushTimer.current = void 0;
159
+ bump();
160
+ }, FLUSH_MS);
161
+ };
162
+ useEffect(() => {
163
+ const s2 = emptyStore(dsKey);
164
+ store.current = s2;
165
+ drawn.current = /* @__PURE__ */ new Set();
166
+ setRosterKey(null);
167
+ bump();
168
+ const gen = ++generation.current;
169
+ if (!ds || !supported) return;
170
+ const dispose = hub.subscribeMembers(ds, {
171
+ roster: (roster) => {
172
+ s2.roster = roster;
173
+ drawn.current = new Set(capMembers(roster, limit).shown.map((m) => m.token));
174
+ setRosterKey(`${gen}
175
+ ${[...drawn.current].join("\n")}`);
176
+ schedule();
177
+ },
178
+ next: (sample) => {
179
+ if (!drawn.current.has(sample.deviceToken)) return;
180
+ let buffer = s2.live.get(sample.deviceToken);
181
+ if (!buffer) s2.live.set(sample.deviceToken, buffer = []);
182
+ pushLive(buffer, sample);
183
+ schedule();
184
+ },
185
+ forbidden: () => {
186
+ s2.forbidden = true;
187
+ schedule();
188
+ },
189
+ error: (err) => {
190
+ s2.error = err;
191
+ schedule();
192
+ },
193
+ reconnected: () => {
194
+ resumed();
195
+ if (boundaryTimer.current !== void 0) return;
196
+ const at = now();
197
+ boundaryTimer.current = setTimeout(() => {
198
+ boundaryTimer.current = void 0;
199
+ resumed();
200
+ }, MINUTE_MS - at % MINUTE_MS + RESEED_SETTLE_MS);
201
+ }
202
+ });
203
+ return () => {
204
+ dispose();
205
+ if (flushTimer.current !== void 0) clearTimeout(flushTimer.current);
206
+ flushTimer.current = void 0;
207
+ if (boundaryTimer.current !== void 0) clearTimeout(boundaryTimer.current);
208
+ boundaryTimer.current = void 0;
209
+ };
210
+ }, [hub, dsKey, supported, limit]);
211
+ useEffect(() => {
212
+ const s2 = store.current;
213
+ if (!supported || !s2.roster || rosterKey === null || !measurement) return;
214
+ const tokens = capMembers(s2.roster, limit).shown.map((m) => m.token);
215
+ if (tokens.length === 0) return;
216
+ const window = seedWindow(spanSeconds, pausedAt ?? now());
217
+ const end = Date.parse(window.endTime);
218
+ let cancelled = false;
219
+ void hub.readMemberSeeds(
220
+ tokens,
221
+ {
222
+ // The plan reads the one measurement over the span and keeps its buckets; the seed it returns
223
+ // is empty because this chart draws the history itself, not a "latest" cell.
224
+ seed: async (token, read) => {
225
+ const buckets = await read({ startTime: window.startTime, endTime: window.endTime, intervalSeconds: window.intervalSeconds, name: measurement });
226
+ if (!cancelled) s2.history.set(token, { samples: bucketSamples(token, measurement, buckets), end });
227
+ return { latest: {}, observed: [] };
228
+ }
229
+ },
230
+ (token, seed, failure) => {
231
+ if (cancelled) return;
232
+ if (failure === "forbidden") s2.forbidden = true;
233
+ else if (!seed && !s2.history.has(token)) s2.history.set(token, "failed");
234
+ schedule();
235
+ },
236
+ () => cancelled
237
+ );
238
+ return () => {
239
+ cancelled = true;
240
+ };
241
+ }, [hub, supported, dsKey, rosterKey, spanSeconds, pausedAt, limit, resumes]);
242
+ useEffect(() => {
243
+ if (pausedAt !== void 0) return;
244
+ const id = setInterval(bump, AGE_OUT_MS);
245
+ return () => clearInterval(id);
246
+ }, [pausedAt]);
247
+ const view = useMemo(() => {
248
+ const s2 = store.current.key === dsKey ? store.current : emptyStore(dsKey);
249
+ const capped2 = s2.roster ? capMembers(s2.roster, limit) : void 0;
250
+ const labels2 = capped2 ? memberLabels(capped2.shown) : /* @__PURE__ */ new Map();
251
+ const until = pausedAt ?? now();
252
+ const composed2 = capped2 && measurement ? composeSeries({ shown: capped2.shown, labels: labels2, measurement, history: s2.history, live: s2.live, since: until - spanSeconds * 1e3, until }) : void 0;
253
+ return { s: s2, capped: capped2, labels: labels2, composed: composed2 };
254
+ }, [version, dsKey, limit, spanSeconds, pausedAt, now, measurement]);
255
+ const { s, capped, labels, composed } = view;
256
+ const freshness = useMemo(
257
+ () => descriptor.freshness === true ? { at: composed?.newest, staleAfterSeconds: resolveStaleAfterSeconds(widget.options) } : void 0,
258
+ [descriptor, widget.options, composed?.newest]
259
+ );
260
+ if (s.error) return /* @__PURE__ */ jsx(ErrorFrame, { widget, error: s.error });
261
+ const title = optString(widget.options, "title");
262
+ const strings = fleetStrings();
263
+ const empty = (message) => /* @__PURE__ */ jsx(WidgetFrame, { title, children: /* @__PURE__ */ jsx(LevelEmpty, { message }) });
264
+ if (!measurement) return empty(strings.noMeasurement);
265
+ if (!supported) return empty(strings.unsupported);
266
+ if (s.forbidden) return empty(strings.forbidden);
267
+ if (ds?.kind === "unbound") return empty(strings.unbound);
268
+ if (!capped) return empty(strings.loading);
269
+ if (capped.shown.length === 0) return empty(strings.noMembers);
270
+ return /* @__PURE__ */ jsx(WidgetFreshnessProvider, { freshness, children: /* @__PURE__ */ jsx(WidgetFrame, { title, flush: true, children: /* @__PURE__ */ jsx(
271
+ CompareBody,
272
+ {
273
+ widget,
274
+ names: capped.shown.map((m) => labels.get(m.token) ?? m.token),
275
+ samples: composed?.samples ?? [],
276
+ bucketSeconds: seedWindow(spanSeconds, pausedAt ?? now()).intervalSeconds,
277
+ capped,
278
+ historyFailed: composed?.historyFailed ?? 0
279
+ }
280
+ ) }) });
281
+ }
282
+ function CompareBody({
283
+ widget,
284
+ names,
285
+ samples,
286
+ bucketSeconds,
287
+ capped,
288
+ historyFailed
289
+ }) {
290
+ const theme = useChartTheme();
291
+ const { size, scale } = useWidgetScale();
292
+ const strings = compareStrings();
293
+ const unit = optString(widget.options, "unit");
294
+ const precision = optNumber(widget.options, "precision");
295
+ const thresholds = useMemo(() => optThresholds(widget.options, "thresholds"), [widget.options]);
296
+ const min = optNumber(widget.options, "min");
297
+ const max = optNumber(widget.options, "max");
298
+ const namesKey = names.join("\n");
299
+ const option = useMemo(
300
+ () => buildLineOption(samples, theme, { measurements: names, unit, precision, thresholds, scale, min, max }, size),
301
+ // `names` is read through `namesKey` (value identity): the array is rebuilt on every render.
302
+ // eslint-disable-next-line react-hooks/exhaustive-deps
303
+ [samples, theme, namesKey, unit, precision, thresholds, scale, size, min, max]
304
+ );
305
+ const bucket = spanLabel(bucketSeconds);
306
+ const compact = size.width > 0 && size.width < COMPACT_WIDTH || size.height > 0 && size.height < COMPACT_HEIGHT;
307
+ const facts = [];
308
+ if (capped.capped) {
309
+ const full = capped.pagedBySource ? strings.cappedPage(capped.shown.length, capped.read, capped.total) : strings.capped(capped.shown.length, capped.total);
310
+ facts.push({ full, short: strings.cappedShort(capped.shown.length, capped.total) });
311
+ }
312
+ if (historyFailed > 0) facts.push({ full: strings.historyFailed(historyFailed), short: strings.historyFailedShort(historyFailed) });
313
+ if (thresholds.length > 0) facts.push({ full: strings.thresholdNote, short: strings.thresholdShort });
314
+ const badge = /* @__PURE__ */ jsx(
315
+ "span",
316
+ {
317
+ "data-compare-bucket": bucket,
318
+ title: strings.bucketedTitle(bucket),
319
+ style: {
320
+ padding: `0 ${spaceVar(1)}`,
321
+ fontSize: fontVar("xs"),
322
+ whiteSpace: "nowrap",
323
+ color: css("muted-foreground"),
324
+ background: css("muted"),
325
+ borderRadius: 4
326
+ },
327
+ children: strings.bucketed(bucket)
328
+ }
329
+ );
330
+ const noteStyle = { flexShrink: 0, padding: `${spaceVar(1)} ${spaceVar(2)}`, fontSize: fontVar("xs"), color: css("muted-foreground") };
331
+ return /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", width: "100%", height: "100%" }, "data-compare-chart": "", children: [
332
+ /* @__PURE__ */ jsx("div", { style: { position: "relative", flex: 1, minHeight: 0 }, children: /* @__PURE__ */ jsx(EChart, { option }) }),
333
+ compact ? /* @__PURE__ */ jsxs("div", { "data-compare-notes": facts.length > 0 ? "" : void 0, "data-compare-compact": "", title: facts.map((f) => f.full).join(" "), style: noteStyle, children: [
334
+ badge,
335
+ facts.map((f) => ` \xB7 ${f.short}`).join("")
336
+ ] }) : /* @__PURE__ */ jsxs("div", { "data-compare-notes": facts.length > 0 ? "" : void 0, title: facts.length > 0 ? facts.map((f) => f.full).join(" ") : void 0, style: noteStyle, children: [
337
+ /* @__PURE__ */ jsx("div", { children: badge }),
338
+ facts.map((f) => /* @__PURE__ */ jsx("div", { children: f.full }, f.full))
339
+ ] })
340
+ ] });
341
+ }
342
+ export {
343
+ FLUSH_MS,
344
+ CompareFeed as default
345
+ };
346
+ //# sourceMappingURL=compare-feed-AY3CVKUZ.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/compare-feed.tsx", "../../src/compare-model.ts", "../../src/compare-strings.ts"],
4
+ "sourcesContent": ["// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// The multi-device compare chart: ONE measurement, a line per member of a group, over the board's\n// span. Its own lazy chunk, loaded from the fleet feed's entry only when a time-series chart is set\n// to one line per device -- a board without one never downloads it.\n//\n// - ONE live subscription (hub.subscribeMembers), then one history read per DRAWN member through\n// hub.readMemberSeeds: bounded hub-wide (MEMBER_SEED_CONCURRENCY) and shared with any widget\n// asking the same question. There is no batched history read in the platform (the store\n// aggregates one device at a time), so the cost of the chart is the number of lines it draws --\n// which is why that number is capped (`maxSeries`) and the cap is said on the chart.\n// - A history read is the measurement alone (the store narrows by name), over the board's span (or\n// the chart's pinned one) ending at the last minute boundary, in buckets of bucketSecondsFor(span):\n// one point per bucket, the bucket's AVERAGE. The chart says so (the badge), and says that\n// threshold lines are references, never a reading of a bucket.\n// - Live readings extend each line past the history's end. While the board is paused the view is\n// cut at the freeze instant; the buffer keeps filling and the chart catches up on resume.\n// - A member stream that resumes after an interruption re-reads the histories, at once and again\n// just after the next minute boundary (as the fleet feed does).\n//\n// Rebuilding the state per sample would re-render the chart hundreds of times a second, so changes\n// are folded into a store and flushed at most once per FLUSH_MS.\n\nimport {\n DEFAULT_TIME_RANGE_SECONDS,\n spanLabel,\n type MemberRoster,\n type MeasurementSample,\n type WidgetDataSource,\n type WidgetDescriptor,\n type WidgetInstance,\n} from '@devicechain/dashboards';\nimport {\n css,\n fontVar,\n optNumber,\n optString,\n optThresholds,\n resolveStaleAfterSeconds,\n spaceVar,\n useWidgetScale,\n useWidgetTimeRange,\n WidgetFrame,\n WidgetFreshnessProvider,\n} from '@devicechain/widget-sdk';\nimport { buildLineOption, EChart, useChartTheme } from '@devicechain/widget-sdk/echarts';\nimport { useEffect, useMemo, useReducer, useRef, useState, type ComponentType } from 'react';\n\nimport { measurementSpan } from './binding-policy';\nimport { bucketSamples, capMembers, composeSeries, memberLabels, pushLive, seriesLimit, type CappedMembers, type MemberHistory } from './compare-model';\nimport { compareStrings } from './compare-strings';\nimport { seedWindow } from './fleet-model';\nimport { fleetStrings } from './fleet-strings';\nimport { LevelEmpty } from './level-readout';\n\nexport const FLUSH_MS = 250;\nconst MINUTE_MS = 60_000;\n// How long past a minute boundary the second re-read after an interruption waits (the fleet feed's).\nconst RESEED_SETTLE_MS = 2_000;\n// A reading ages out of the span with no event to say so.\nconst AGE_OUT_MS = 30_000;\n// Below this width or height (px) the notes under the chart are said in a line.\nconst COMPACT_WIDTH = 320;\nconst COMPACT_HEIGHT = 200;\n\ninterface Store {\n key: string | null;\n roster: MemberRoster | null;\n live: Map<string, MeasurementSample[]>;\n history: Map<string, MemberHistory | 'failed'>;\n forbidden: boolean;\n error: unknown | null;\n}\n\nconst emptyStore = (key: string | null): Store => ({ key, roster: null, live: new Map(), history: new Map(), forbidden: false, error: null });\n\nexport interface CompareFeedProps {\n hub: WidgetDataSource;\n widget: WidgetInstance;\n descriptor: WidgetDescriptor;\n ErrorFrame: ComponentType<{ widget: WidgetInstance; error: unknown }>;\n}\n\nexport default function CompareFeed({ hub, widget, descriptor, ErrorFrame }: CompareFeedProps) {\n const range = useWidgetTimeRange();\n const now = useMemo(() => (hub.now ? () => hub.now!() : Date.now), [hub]);\n const spanSeconds = measurementSpan(descriptor, widget, range?.spanSeconds) ?? range?.spanSeconds ?? DEFAULT_TIME_RANGE_SECONDS;\n const pausedAt = range?.paused ? range.pausedAt : undefined;\n const measurement = optString(widget.options, 'measurement') ?? widget.datasource?.measurements[0];\n const limit = seriesLimit(optNumber(widget.options, 'maxSeries'));\n // The datasource the live feed is asked for: the one measurement, so the hub delivers no other.\n const ds = widget.datasource && measurement ? { ...widget.datasource, measurements: [measurement] } : undefined;\n const dsKey = JSON.stringify(ds ?? null);\n const supported = hub.subscribeMembers !== undefined && hub.readMemberSeeds !== undefined;\n\n const store = useRef<Store>(emptyStore(dsKey));\n const [version, bump] = useReducer((n: number) => n + 1, 0);\n const [rosterKey, setRosterKey] = useState<string | null>(null);\n const generation = useRef(0);\n const flushTimer = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);\n const boundaryTimer = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);\n const [resumes, resumed] = useReducer((n: number) => n + 1, 0);\n // The tokens drawn, which the live feed filters by.\n const drawn = useRef<Set<string>>(new Set());\n\n const schedule = (): void => {\n if (flushTimer.current !== undefined) return;\n flushTimer.current = setTimeout(() => {\n flushTimer.current = undefined;\n bump();\n }, FLUSH_MS);\n };\n\n useEffect(() => {\n const s = emptyStore(dsKey);\n store.current = s;\n drawn.current = new Set();\n setRosterKey(null);\n bump();\n const gen = ++generation.current;\n if (!ds || !supported) return;\n const dispose = hub.subscribeMembers!(ds, {\n roster: (roster) => {\n s.roster = roster;\n drawn.current = new Set(capMembers(roster, limit).shown.map((m) => m.token));\n setRosterKey(`${gen}\\n${[...drawn.current].join('\\n')}`);\n schedule();\n },\n next: (sample) => {\n if (!drawn.current.has(sample.deviceToken)) return;\n let buffer = s.live.get(sample.deviceToken);\n if (!buffer) s.live.set(sample.deviceToken, (buffer = []));\n pushLive(buffer, sample);\n schedule();\n },\n forbidden: () => {\n s.forbidden = true;\n schedule();\n },\n error: (err) => {\n s.error = err;\n schedule();\n },\n reconnected: () => {\n resumed();\n if (boundaryTimer.current !== undefined) return;\n const at = now();\n boundaryTimer.current = setTimeout(() => {\n boundaryTimer.current = undefined;\n resumed();\n }, MINUTE_MS - (at % MINUTE_MS) + RESEED_SETTLE_MS);\n },\n });\n return () => {\n dispose();\n if (flushTimer.current !== undefined) clearTimeout(flushTimer.current);\n flushTimer.current = undefined;\n if (boundaryTimer.current !== undefined) clearTimeout(boundaryTimer.current);\n boundaryTimer.current = undefined;\n };\n // `ds` is read through `dsKey` (value identity); a new limit re-draws from the same roster below.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [hub, dsKey, supported, limit]);\n\n // One history read per drawn member, again when the span or the freeze instant changes.\n useEffect(() => {\n const s = store.current;\n if (!supported || !s.roster || rosterKey === null || !measurement) return;\n const tokens = capMembers(s.roster, limit).shown.map((m) => m.token);\n if (tokens.length === 0) return;\n const window = seedWindow(spanSeconds, pausedAt ?? now());\n const end = Date.parse(window.endTime);\n let cancelled = false;\n void hub.readMemberSeeds!(\n tokens,\n {\n // The plan reads the one measurement over the span and keeps its buckets; the seed it returns\n // is empty because this chart draws the history itself, not a \"latest\" cell.\n seed: async (token, read) => {\n const buckets = await read({ startTime: window.startTime, endTime: window.endTime, intervalSeconds: window.intervalSeconds, name: measurement });\n // A read this effect no longer wants (the span, the freeze instant or the roster moved on\n // while it was in flight) must not overwrite the newer one's history when it lands last.\n if (!cancelled) s.history.set(token, { samples: bucketSamples(token, measurement, buckets), end });\n return { latest: {}, observed: [] };\n },\n },\n (token, seed, failure) => {\n if (cancelled) return;\n if (failure === 'forbidden') s.forbidden = true;\n // A re-read that failed keeps the history it would have replaced: those points are still true.\n else if (!seed && !s.history.has(token)) s.history.set(token, 'failed');\n schedule();\n },\n () => cancelled,\n );\n return () => {\n cancelled = true;\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [hub, supported, dsKey, rosterKey, spanSeconds, pausedAt, limit, resumes]);\n\n // A reading ages out of the span with no event to say so.\n useEffect(() => {\n if (pausedAt !== undefined) return;\n const id = setInterval(bump, AGE_OUT_MS);\n return () => clearInterval(id);\n }, [pausedAt]);\n\n const view = useMemo(() => {\n const s = store.current.key === dsKey ? store.current : emptyStore(dsKey);\n const capped = s.roster ? capMembers(s.roster, limit) : undefined;\n const labels = capped ? memberLabels(capped.shown) : new Map<string, string>();\n const until = pausedAt ?? now();\n const composed =\n capped && measurement\n ? composeSeries({ shown: capped.shown, labels, measurement, history: s.history, live: s.live, since: until - spanSeconds * 1000, until })\n : undefined;\n return { s, capped, labels, composed };\n // `version` is the store's change counter; the store itself is mutable.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [version, dsKey, limit, spanSeconds, pausedAt, now, measurement]);\n\n const { s, capped, labels, composed } = view;\n const freshness = useMemo(\n () => (descriptor.freshness === true ? { at: composed?.newest, staleAfterSeconds: resolveStaleAfterSeconds(widget.options) } : undefined),\n [descriptor, widget.options, composed?.newest],\n );\n if (s.error) return <ErrorFrame widget={widget} error={s.error} />;\n\n const title = optString(widget.options, 'title');\n const strings = fleetStrings();\n const empty = (message: string) => (\n <WidgetFrame title={title}>\n <LevelEmpty message={message} />\n </WidgetFrame>\n );\n if (!measurement) return empty(strings.noMeasurement);\n if (!supported) return empty(strings.unsupported);\n if (s.forbidden) return empty(strings.forbidden);\n if (ds?.kind === 'unbound') return empty(strings.unbound);\n if (!capped) return empty(strings.loading);\n if (capped.shown.length === 0) return empty(strings.noMembers);\n\n return (\n <WidgetFreshnessProvider freshness={freshness}>\n <WidgetFrame title={title} flush>\n <CompareBody\n widget={widget}\n names={capped.shown.map((m) => labels.get(m.token) ?? m.token)}\n samples={composed?.samples ?? []}\n bucketSeconds={seedWindow(spanSeconds, pausedAt ?? now()).intervalSeconds}\n capped={capped}\n historyFailed={composed?.historyFailed ?? 0}\n />\n </WidgetFrame>\n </WidgetFreshnessProvider>\n );\n}\n\nfunction CompareBody({\n widget,\n names,\n samples,\n bucketSeconds,\n capped,\n historyFailed,\n}: {\n widget: WidgetInstance;\n names: string[];\n samples: MeasurementSample[];\n bucketSeconds: number;\n capped: CappedMembers;\n historyFailed: number;\n}) {\n const theme = useChartTheme();\n const { size, scale } = useWidgetScale();\n const strings = compareStrings();\n const unit = optString(widget.options, 'unit');\n const precision = optNumber(widget.options, 'precision');\n const thresholds = useMemo(() => optThresholds(widget.options, 'thresholds'), [widget.options]);\n const min = optNumber(widget.options, 'min');\n const max = optNumber(widget.options, 'max');\n const namesKey = names.join('\\n');\n const option = useMemo(\n () => buildLineOption(samples, theme, { measurements: names, unit, precision, thresholds, scale, min, max }, size),\n // `names` is read through `namesKey` (value identity): the array is rebuilt on every render.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [samples, theme, namesKey, unit, precision, thresholds, scale, size, min, max],\n );\n const bucket = spanLabel(bucketSeconds);\n // The sentences, or (in a box too narrow for them: the widget's smallest span is 4 cells) the same\n // facts in a line, with the sentences in its title. Nothing is dropped, only shortened.\n const compact = (size.width > 0 && size.width < COMPACT_WIDTH) || (size.height > 0 && size.height < COMPACT_HEIGHT);\n const facts: Array<{ full: string; short: string }> = [];\n if (capped.capped) {\n const full = capped.pagedBySource ? strings.cappedPage(capped.shown.length, capped.read, capped.total) : strings.capped(capped.shown.length, capped.total);\n facts.push({ full, short: strings.cappedShort(capped.shown.length, capped.total) });\n }\n if (historyFailed > 0) facts.push({ full: strings.historyFailed(historyFailed), short: strings.historyFailedShort(historyFailed) });\n if (thresholds.length > 0) facts.push({ full: strings.thresholdNote, short: strings.thresholdShort });\n const badge = (\n <span\n data-compare-bucket={bucket}\n title={strings.bucketedTitle(bucket)}\n style={{\n padding: `0 ${spaceVar(1)}`,\n fontSize: fontVar('xs'),\n whiteSpace: 'nowrap',\n color: css('muted-foreground'),\n background: css('muted'),\n borderRadius: 4,\n }}\n >\n {strings.bucketed(bucket)}\n </span>\n );\n const noteStyle = { flexShrink: 0, padding: `${spaceVar(1)} ${spaceVar(2)}`, fontSize: fontVar('xs'), color: css('muted-foreground') } as const;\n return (\n <div style={{ display: 'flex', flexDirection: 'column', width: '100%', height: '100%' }} data-compare-chart=\"\">\n <div style={{ position: 'relative', flex: 1, minHeight: 0 }}>\n <EChart option={option} />\n </div>\n {/* Under the chart, never over it: a corner of the plot is where the legend runs once it fills\n the width (five devices on a phone) and where a threshold's label sits, and the badge\n covered both. */}\n {compact ? (\n <div data-compare-notes={facts.length > 0 ? '' : undefined} data-compare-compact=\"\" title={facts.map((f) => f.full).join(' ')} style={noteStyle}>\n {badge}\n {facts.map((f) => ` \u00B7 ${f.short}`).join('')}\n </div>\n ) : (\n <div data-compare-notes={facts.length > 0 ? '' : undefined} title={facts.length > 0 ? facts.map((f) => f.full).join(' ') : undefined} style={noteStyle}>\n <div>{badge}</div>\n {facts.map((f) => (\n <div key={f.full}>{f.full}</div>\n ))}\n </div>\n )}\n </div>\n );\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// The multi-device compare chart's state, decided apart from the drawing and from the hub: which\n// members get a line, what each line is called, and which readings make it up. Pure, so each rule\n// is a test.\n//\n// A line is one member's readings of ONE measurement over the span: its stored history (one point\n// per bucket, the bucket's AVERAGE) followed by the readings that arrived live after the history\n// was read (raw). Everything is keyed by device; nothing here looks a value up by name alone.\n\nimport type { MeasurementBucket, MeasurementSample, MemberRef } from '@devicechain/dashboards';\n\n// The lines drawn when the author sets no limit: the chart palette's width, so no two lines share\n// a colour. The most an author may ask for: a comparison past this is a table, not a chart.\nexport const DEFAULT_MAX_SERIES = 5;\nexport const MAX_SERIES_LIMIT = 12;\n\n// Live readings kept per member. A comparison is a few lines over a span; this bounds the memory of\n// a chatty device (a reading a second over a week) without touching the stored history, which is\n// already bucketed to at most a couple of hundred points.\nexport const LIVE_PER_MEMBER = 600;\n\n// The limit as the author set it, held to what the chart supports. Unset, or anything that is not a\n// whole number of at least one, is the default: a typo must not turn into a different chart.\nexport function seriesLimit(value: number | undefined): number {\n if (value === undefined || !Number.isInteger(value) || value < 1) return DEFAULT_MAX_SERIES;\n return Math.min(value, MAX_SERIES_LIMIT);\n}\n\nexport interface CappedMembers {\n shown: MemberRef[];\n // The size of the group, as the source reports it. Never less than the members read.\n total: number;\n // True when lines were left out, for either reason: the author's limit, or a group larger than\n // the page the source read (shown + not drawn < total).\n capped: boolean;\n // True when the source itself read less than the group: the total is then the group's size and\n // `read` the members it listed.\n pagedBySource: boolean;\n read: number;\n}\n\n// The first `limit` members of the roster, in the roster's order (a stable order, so a line keeps\n// its colour as readings arrive). Which members are left out is the roster's, not the readings':\n// choosing \"the busiest\" would reshuffle the chart as data moves.\nexport function capMembers(roster: { members: readonly MemberRef[]; total: number }, limit: number): CappedMembers {\n const read = roster.members.length;\n const total = Math.max(roster.total, read);\n const shown = roster.members.slice(0, limit);\n return { shown, total, capped: shown.length < total, pagedBySource: total > read, read };\n}\n\n// A name per member: its device name, else its token. Two members with the same name get their\n// token appended, so two lines never carry one legend entry.\nexport function memberLabels(members: readonly MemberRef[]): Map<string, string> {\n const base = members.map((m) => m.name?.trim() || m.token);\n const count = new Map<string, number>();\n for (const b of base) count.set(b, (count.get(b) ?? 0) + 1);\n const labels = new Map<string, string>();\n members.forEach((m, i) => labels.set(m.token, (count.get(base[i]) ?? 0) > 1 && base[i] !== m.token ? `${base[i]} (${m.token})` : base[i]));\n return labels;\n}\n\n// One member's stored history as samples: a point per bucket that has an average, stamped at the\n// bucket's start, oldest first. A bucket with no readings has no average and no point.\nexport function bucketSamples(token: string, measurement: string, buckets: readonly MeasurementBucket[]): MeasurementSample[] {\n return buckets\n .filter((b) => b.name === measurement && b.avg != null)\n .map((b) => ({\n id: `cmp-${token}-${b.name}-${b.bucketStart}`,\n deviceToken: token,\n eventType: 0,\n occurredTime: b.bucketStart,\n name: b.name,\n value: b.avg,\n classifier: null,\n }))\n .sort((a, b) => Date.parse(a.occurredTime as string) - Date.parse(b.occurredTime as string));\n}\n\n// A member's stored history once read: the points, and the instant the read ended (epoch ms), after\n// which live readings are the only source.\nexport interface MemberHistory {\n samples: MeasurementSample[];\n end: number;\n}\n\n// Appends a live reading to a member's buffer: newest wins on a repeat id, and the buffer keeps the\n// newest LIVE_PER_MEMBER.\nexport function pushLive(buffer: MeasurementSample[], sample: MeasurementSample): void {\n if (buffer.some((s) => s.id === sample.id)) return;\n buffer.push(sample);\n if (buffer.length > LIVE_PER_MEMBER) buffer.splice(0, buffer.length - LIVE_PER_MEMBER);\n}\n\nexport interface ComposeInputs {\n shown: readonly MemberRef[];\n labels: ReadonlyMap<string, string>;\n measurement: string;\n history: ReadonlyMap<string, MemberHistory | 'failed'>;\n live: ReadonlyMap<string, readonly MeasurementSample[]>;\n // The span, epoch ms: nothing before `since` or after `until` is drawn (the board's freeze\n // instant while paused).\n since: number;\n until: number;\n}\n\nexport interface Composed {\n // One sample per point, named by the member's LABEL so the line chart draws one line per member.\n samples: MeasurementSample[];\n // Members whose stored history could not be read: their lines hold live readings only.\n historyFailed: number;\n // The newest reading drawn, epoch ms, for the frame's age.\n newest: number | undefined;\n}\n\nexport function composeSeries(i: ComposeInputs): Composed {\n const samples: MeasurementSample[] = [];\n let historyFailed = 0;\n let newest: number | undefined;\n for (const m of i.shown) {\n const label = i.labels.get(m.token) ?? m.token;\n const h = i.history.get(m.token);\n if (h === 'failed') historyFailed += 1;\n const stored = h && h !== 'failed' ? h.samples : [];\n // Live readings count only after the stored history's read ended: before it, the history holds\n // them already (as an average), and drawing both would put two values on one instant.\n const after = h && h !== 'failed' ? h.end : Number.NEGATIVE_INFINITY;\n const live = (i.live.get(m.token) ?? []).filter((s) => s.name === i.measurement && s.value != null && s.occurredTime != null && Date.parse(s.occurredTime) > after);\n const points = [...stored, ...live]\n .filter((s) => {\n const t = Date.parse(s.occurredTime as string);\n return t >= i.since && t <= i.until;\n })\n .sort((a, b) => Date.parse(a.occurredTime as string) - Date.parse(b.occurredTime as string));\n for (const s of points) {\n samples.push({ ...s, name: label });\n const t = Date.parse(s.occurredTime as string);\n if (newest === undefined || t > newest) newest = t;\n }\n }\n return { samples, historyFailed, newest };\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// The fixed words the multi-device compare chart renders itself. The widget packages carry no i18n\n// runtime, so this is the one small per-language table (modelled on fleet-strings.ts); the option\n// labels are translated in the console's catalogs, not here. Loaded with the compare feed (a lazy\n// chunk), never by a board without a compare chart.\n\nimport { viewerLocale } from '@devicechain/widget-sdk';\n\nexport interface CompareStrings {\n // The group is larger than the lines drawn: say how many, and which ones.\n capped: (shown: number, total: number) => string;\n // The group was larger than what the resolver read: the total is a floor, not the size.\n cappedPage: (shown: number, read: number, total: number) => string;\n // History is a per-bucket average; the live tail is the raw readings.\n bucketed: (bucket: string) => string;\n bucketedTitle: (bucket: string) => string;\n // A threshold line is a reference drawn at a value, never a verdict on an average.\n thresholdNote: string;\n // Some devices' history could not be read: their lines hold only what arrived live.\n historyFailed: (n: number) => string;\n // The same facts for a box too narrow for the sentences above; the full text stays in the title.\n cappedShort: (shown: number, total: number) => string;\n thresholdShort: string;\n historyFailedShort: (n: number) => string;\n}\n\nexport const COMPARE_STRINGS: Record<'en' | 'es', CompareStrings> = {\n en: {\n capped: (shown, total) => `Showing ${shown} of ${total} devices (the first ${shown}); raise Most lines, up to 12, to see more.`,\n cappedPage: (shown, read, total) =>\n `Showing ${shown} of ${total} devices: the group is larger than the ${read} devices read, and the first ${shown} are drawn.`,\n bucketed: (bucket) => `avg per ${bucket}`,\n bucketedTitle: (bucket) =>\n `Stored history is drawn as the average of each ${bucket} bucket, one point per bucket; readings that arrived live since are drawn as they came, unaveraged.`,\n thresholdNote: 'Thresholds are reference lines; a plotted history point is a bucket average, not a reading.',\n historyFailed: (n) => (n === 1 ? '1 device\u2019s history could not be read; its line starts from live readings.' : `${n} devices\u2019 history could not be read; their lines start from live readings.`),\n cappedShort: (shown, total) => `${shown} of ${total} devices`,\n thresholdShort: 'thresholds are references',\n historyFailedShort: (n) => `${n} unread`,\n },\n es: {\n capped: (shown, total) =>\n `Mostrando ${shown} de ${total} dispositivos (los primeros ${shown}); aumente M\u00E1ximo de l\u00EDneas, hasta 12, para ver m\u00E1s.`,\n cappedPage: (shown, read, total) =>\n `Mostrando ${shown} de ${total} dispositivos: el grupo es mayor que los ${read} dispositivos le\u00EDdos, y se dibujan los primeros ${shown}.`,\n bucketed: (bucket) => `prom. por ${bucket}`,\n bucketedTitle: (bucket) =>\n `El historial almacenado se dibuja como el promedio de cada intervalo de ${bucket}, un punto por intervalo; las lecturas llegadas en vivo desde entonces se dibujan tal cual, sin promediar.`,\n thresholdNote: 'Los umbrales son l\u00EDneas de referencia; un punto de historial es un promedio por intervalo, no una lectura.',\n historyFailed: (n) =>\n n === 1 ? 'No se pudo leer el historial de 1 dispositivo; su l\u00EDnea parte de lecturas en vivo.' : `No se pudo leer el historial de ${n} dispositivos; sus l\u00EDneas parten de lecturas en vivo.`,\n cappedShort: (shown, total) => `${shown} de ${total} dispositivos`,\n thresholdShort: 'umbrales son referencias',\n historyFailedShort: (n) => `${n} sin leer`,\n },\n};\n\nexport function compareStrings(locale: string | undefined = viewerLocale()): CompareStrings {\n return locale?.toLowerCase().split('-')[0] === 'es' ? COMPARE_STRINGS.es : COMPARE_STRINGS.en;\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["s", "capped", "labels", "composed"]
7
+ }