@devicechain/widgets 0.24.0 → 0.25.0-rc.2
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 +11 -1
- package/dist/absolute-range-picker.d.ts +22 -0
- package/dist/binding-policy.d.ts +1 -0
- package/dist/builtin-descriptors.d.ts +69 -0
- package/dist/builtin-registry.d.ts +68 -0
- package/dist/chunks/absolute-range-picker-E57G2TPX.js +139 -0
- package/dist/chunks/absolute-range-picker-E57G2TPX.js.map +7 -0
- package/dist/chunks/{aggregate-feed-4FSBQYJL.js → aggregate-feed-XX36GNY6.js} +2 -2
- package/dist/chunks/{aggregate-widgets-2ZQBW3YF.js → aggregate-widgets-72HMPMBU.js} +99 -57
- package/dist/chunks/aggregate-widgets-72HMPMBU.js.map +7 -0
- package/dist/chunks/{chunk-USRYKW2J.js → chunk-7BUCJQFP.js} +10 -2
- package/dist/chunks/chunk-7BUCJQFP.js.map +7 -0
- package/dist/chunks/chunk-AF4M5OXQ.js +23 -0
- package/dist/chunks/chunk-AF4M5OXQ.js.map +7 -0
- package/dist/chunks/{chunk-XQQQNTBX.js → chunk-EQOWBDS7.js} +1 -1
- package/dist/chunks/{chunk-XQQQNTBX.js.map → chunk-EQOWBDS7.js.map} +1 -1
- package/dist/chunks/chunk-JVC6JYUR.js +48 -0
- package/dist/chunks/chunk-JVC6JYUR.js.map +7 -0
- package/dist/chunks/{chunk-JFH4QMLA.js → chunk-NYHNQ5VR.js} +5 -1
- package/dist/chunks/{chunk-JFH4QMLA.js.map → chunk-NYHNQ5VR.js.map} +2 -2
- package/dist/chunks/chunk-VTGKDO44.js +87 -0
- package/dist/chunks/chunk-VTGKDO44.js.map +7 -0
- package/dist/chunks/chunk-XJ7UF2CP.js +90 -0
- package/dist/chunks/chunk-XJ7UF2CP.js.map +7 -0
- package/dist/chunks/{compare-feed-AY3CVKUZ.js → compare-feed-5OLZHFRL.js} +48 -23
- package/dist/chunks/compare-feed-5OLZHFRL.js.map +7 -0
- package/dist/chunks/{event-markers-feed-GEGTIHXT.js → event-markers-feed-B3MVFYUT.js} +4 -4
- package/dist/chunks/export-rows-OEWIBIUY.js +11 -0
- package/dist/chunks/{fleet-feed-2SKDR6ZQ.js → fleet-feed-D4T3LC25.js} +10 -10
- package/dist/chunks/history-widgets-NSNXDKEC.js +336 -0
- package/dist/chunks/history-widgets-NSNXDKEC.js.map +7 -0
- package/dist/chunks/{level-widgets-MJB7TOLU.js → level-widgets-HE6EDSWC.js} +13 -9
- package/dist/chunks/level-widgets-HE6EDSWC.js.map +7 -0
- package/dist/chunks/no-reading-B4MPXAS2.js +22 -0
- package/dist/chunks/no-reading-B4MPXAS2.js.map +7 -0
- package/dist/chunks/{presence-widgets-2LIUKN4Y.js → presence-widgets-M5VGG3OO.js} +12 -8
- package/dist/chunks/presence-widgets-M5VGG3OO.js.map +7 -0
- package/dist/chunks/table-CEVB3JZS.js +8 -0
- package/dist/chunks/table-CEVB3JZS.js.map +7 -0
- package/dist/compare-model.d.ts +1 -1
- package/dist/documented-descriptors.d.ts +206 -0
- package/dist/export-rows.d.ts +24 -0
- package/dist/history-feed.d.ts +10 -0
- package/dist/history-model.d.ts +45 -0
- package/dist/history-strings.d.ts +20 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +544 -189
- package/dist/index.js.map +4 -4
- package/dist/level-reading.d.ts +1 -0
- package/dist/no-reading-core.d.ts +25 -0
- package/dist/no-reading.d.ts +10 -0
- package/dist/time-range-bar.d.ts +26 -2
- package/dist/widgets/history-lazy.d.ts +70 -0
- package/dist/widgets/history-table.d.ts +7 -0
- package/dist/widgets/history-widgets.d.ts +2 -0
- package/dist/widgets/no-reading-lazy.d.ts +7 -0
- package/package.json +8 -8
- package/dist/chunks/aggregate-widgets-2ZQBW3YF.js.map +0 -7
- package/dist/chunks/chunk-USRYKW2J.js.map +0 -7
- package/dist/chunks/compare-feed-AY3CVKUZ.js.map +0 -7
- package/dist/chunks/level-widgets-MJB7TOLU.js.map +0 -7
- package/dist/chunks/presence-widgets-2LIUKN4Y.js.map +0 -7
- package/dist/chunks/table-6EVV7QZH.js +0 -7
- /package/dist/chunks/{aggregate-feed-4FSBQYJL.js.map → aggregate-feed-XX36GNY6.js.map} +0 -0
- /package/dist/chunks/{event-markers-feed-GEGTIHXT.js.map → event-markers-feed-B3MVFYUT.js.map} +0 -0
- /package/dist/chunks/{table-6EVV7QZH.js.map → export-rows-OEWIBIUY.js.map} +0 -0
- /package/dist/chunks/{fleet-feed-2SKDR6ZQ.js.map → fleet-feed-D4T3LC25.js.map} +0 -0
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/widgets/bar-chart.tsx", "../../src/aggregate-strings.ts", "../../src/category-items.ts", "../../src/widgets/fleet-count.tsx", "../../src/fleet-count-model.ts", "../../src/widgets/fleet-table.tsx", "../../src/widgets/pie-chart.tsx", "../../src/widgets/stat-card.tsx", "../../src/aggregate-reading.ts", "../../src/sparkline.tsx"],
|
|
4
|
+
"sourcesContent": ["// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// bar-chart -- one bar per measurement, each as tall as that measurement's latest reading. The\n// categories are the measurements the datasource names (in that order, or sorted by value); one\n// that has not reported yet keeps its label with no bar rather than vanishing or reading zero.\n//\n// Drawn as SVG with its text as DOM text nodes, not on an ECharts canvas: no chart type to\n// register (a bar or pie type costs ~7 KB gzip each, and a second lazily loaded ECharts module\n// makes the app bundler split the whole chart engine out of the entry chunk, which cost more than\n// the types themselves), and the readability gate can measure the labels.\n\nimport { useMemo } from 'react';\n\nimport {\n css,\n formatValue,\n optNumber,\n optString,\n WidgetFrame,\n type WidgetProps,\n type WidgetScale,\n} from '@devicechain/widget-sdk';\n\nimport { aggregateStrings } from '../aggregate-strings';\nimport { categoryItems, parseSort } from '../category-items';\nimport { deviceBars } from '../fleet-model';\nimport { fleetStrings } from '../fleet-strings';\nimport { niceDomain } from '../category-geometry';\nimport { LevelEmpty } from '../level-readout';\nimport { Explained } from '../no-reading';\n\n// The width of a character of the chart font, as a share of its size: an estimate for the room a\n// label needs. Wide enough for digits and capitals, so a label is trimmed rather than overlapped.\nconst CHAR_EM = 0.6;\n// Past this many categories the value labels on the bars would collide.\nexport const MAX_LABELLED_BARS = 12;\n// The thickest a bar is drawn, in px at density 1: a single bar does not fill the chart.\nconst MAX_BAR_PX = 56;\n\n// A chart of one bar per device cannot label hundreds of bars; the first MAX_DEVICE_BARS in the\n// chosen order are drawn, and the chart SAYS how many devices there are (see deviceItems).\nexport const MAX_DEVICE_BARS = 100;\n\nexport function BarChart({ widget, data }: WidgetProps) {\n const sort = parseSort(optString(widget.options, 'sort'));\n const perDevice = optString(widget.options, 'groupBy') === 'device';\n const measurement = optString(widget.options, 'measurement') ?? widget.datasource?.measurements[0];\n const items = useMemo(\n () => (perDevice ? [] : categoryItems(widget, data, sort)),\n [widget, data, sort, perDevice],\n );\n const unit = optString(widget.options, 'unit');\n const precision = optNumber(widget.options, 'precision');\n return (\n <WidgetFrame title={optString(widget.options, 'title')}>\n {(ws) => {\n if (perDevice) {\n return <DeviceBars data={data} measurement={measurement} sort={sort} unit={unit} precision={precision} ws={ws} />;\n }\n return items.length === 0 ? (\n <LevelEmpty message={aggregateStrings().noCategories} />\n ) : (\n <Bars items={items} unit={unit} precision={precision} ws={ws} />\n );\n }}\n </WidgetFrame>\n );\n}\n\n// One bar per device of the anchor, for one measurement: the same chart, with the fleet's members as\n// its categories. Every way the fleet can fail to be a chart is a sentence, and a fleet larger than\n// what is drawn is counted in the chart itself, where it cannot be dropped with a heading.\nfunction DeviceBars({\n data,\n measurement,\n sort,\n unit,\n precision,\n ws,\n}: {\n data: WidgetProps['data'];\n measurement: string | undefined;\n sort: ReturnType<typeof parseSort>;\n unit: string | undefined;\n precision: number | undefined;\n ws: WidgetScale;\n}) {\n const strings = fleetStrings();\n const fleet = data.members;\n const bars = useMemo(\n () => (fleet && measurement ? deviceBars(fleet.members, measurement, sort) : []),\n [fleet, measurement, sort],\n );\n if (!measurement) return <LevelEmpty message={strings.noMeasurement} />;\n if (!fleet || fleet.status === 'loading') return <LevelEmpty message={strings.loading} />;\n if (fleet.status === 'unbound') return <Explained kind=\"unbound\" message={strings.unbound} />;\n if (fleet.status === 'forbidden') return <Explained kind=\"forbidden\" message={strings.forbidden} />;\n if (fleet.status === 'unsupported') return <LevelEmpty message={strings.unsupported} />;\n if (bars.length === 0) return <LevelEmpty message={strings.noMembers} />;\n const drawn = bars.slice(0, MAX_DEVICE_BARS);\n // Counted against the anchor, not only against this chart's own cap: a fleet the resolver read\n // only a page of is just as cut off as one this chart trimmed.\n const note = fleet.truncated || drawn.length < bars.length ? strings.truncated(drawn.length, Math.max(fleet.total, bars.length)) : undefined;\n return (\n <Bars\n items={drawn.map((b) => ({ label: b.label, key: b.token, value: b.value }))}\n unit={unit}\n precision={precision}\n ws={ws}\n note={note}\n />\n );\n}\n\n// \"abcdefgh\" fitted to `chars` characters: cut and ended with an ellipsis. The full text stays in\n// the element's <title>, so nothing is lost to the trim.\nexport function fitLabel(label: string, chars: number): string {\n if (chars < 2) return '';\n return label.length <= chars ? label : `${label.slice(0, Math.max(1, chars - 1))}\u2026`;\n}\n\nfunction Bars({\n items,\n unit,\n precision,\n ws,\n note,\n}: {\n items: { label: string; value: number | null; key?: string }[];\n unit: string | undefined;\n precision: number | undefined;\n ws: WidgetScale;\n // A line along the bottom of the chart: how much of the fleet the bars are.\n note?: string;\n}) {\n const W = ws.size.width > 0 ? ws.size.width : 240;\n const fullH = ws.size.height > 0 ? ws.size.height : 160;\n const noteH = note ? ws.px('font-xs') * 1.8 : 0;\n const H = Math.max(1, fullH - noteH);\n // Columns in a wide chart, rows in a tall one: rows leave a long measurement name its full width.\n const horizontal = H > W;\n const font = ws.px('font-xs');\n const pad = ws.px('space-1');\n const charPx = font * CHAR_EM;\n const fmt = (v: number) => formatValue(v, precision);\n const withUnit = (v: number) => (unit ? `${fmt(v)} ${unit}` : fmt(v));\n const domain = niceDomain(items.flatMap((i) => (i.value === null ? [] : [i.value])));\n const span = domain.max - domain.min;\n const few = items.length <= MAX_LABELLED_BARS;\n const widest = (texts: string[]) => texts.reduce((n, t) => Math.max(n, t.length), 0) * charPx;\n const tickText = domain.ticks.map(fmt);\n\n // The plot box. Vertical: value ticks down the left, category labels along the bottom, room\n // above for the unit and the tallest bar's value. Horizontal: category labels down the left,\n // value ticks along the bottom, room to the right for the longest bar's value.\n const catLabelH = font * 1.7;\n const topH = font * 1.7;\n let x0: number, x1: number, y0: number, y1: number;\n if (horizontal) {\n x0 = Math.min(W * 0.4, widest(items.map((i) => i.label)) + pad * 2);\n x1 = W - pad - (few ? widest(items.map((i) => (i.value === null ? '' : withUnit(i.value)))) + pad : pad);\n y0 = pad;\n y1 = H - catLabelH;\n } else {\n x0 = widest(tickText) + pad * 2;\n x1 = W - pad;\n y0 = topH;\n y1 = H - catLabelH;\n }\n const length = horizontal ? Math.max(0, x1 - x0) : Math.max(0, y1 - y0);\n // A value's distance along the value axis from the plot's low end (left, or bottom).\n const along = (v: number) => ((v - domain.min) / span) * length;\n const bandPx = (horizontal ? y1 - y0 : x1 - x0) / items.length;\n // Each bar's value is printed beside it when the labels fit: no more than MAX_LABELLED_BARS, and,\n // in columns, the widest value no wider than a column (side by side they would run together).\n // Otherwise every value is still in the chart's accessible description and on each bar's title.\n const labelled =\n few &&\n (horizontal || widest(items.map((i) => (i.value === null ? '' : fmt(i.value)))) <= bandPx);\n const thick = Math.min(bandPx * 0.62, MAX_BAR_PX * ws.scale);\n const muted = { fill: css('muted-foreground') };\n const text = { fontSize: font, fontVariantNumeric: 'tabular-nums' } as const;\n const zero = along(0);\n\n return (\n <svg\n data-bar-chart={horizontal ? 'horizontal' : 'vertical'}\n width={W}\n height={H}\n viewBox={`0 0 ${W} ${H}`}\n role=\"img\"\n aria-label={items.map((i) => `${i.label}: ${i.value === null ? '\u2014' : withUnit(i.value)}`).join(', ')}\n style={{ display: 'block' }}\n >\n {note ? (\n <text data-bar-note=\"\" x={pad} y={fullH - font * 0.4} style={{ ...text, ...muted }}>\n {note}\n </text>\n ) : null}\n {unit ? (\n <text\n x={horizontal ? W - pad : pad}\n y={horizontal ? H - pad : font}\n textAnchor={horizontal ? 'end' : 'start'}\n style={{ ...text, ...muted }}\n >\n {unit}\n </text>\n ) : null}\n {domain.ticks.map((t, i) => {\n const d = along(t);\n return horizontal ? (\n <g key={t}>\n <line x1={x0 + d} x2={x0 + d} y1={y0} y2={y1} style={{ stroke: css('border') }} />\n <text x={x0 + d} y={y1 + font * 1.2} textAnchor=\"middle\" style={{ ...text, ...muted }}>\n {tickText[i]}\n </text>\n </g>\n ) : (\n <g key={t}>\n <line x1={x0} x2={x1} y1={y1 - d} y2={y1 - d} style={{ stroke: css('border') }} />\n <text x={x0 - pad} y={y1 - d + font * 0.35} textAnchor=\"end\" style={{ ...text, ...muted }}>\n {tickText[i]}\n </text>\n </g>\n );\n })}\n {items.map((item, i) => {\n const centre = (horizontal ? y0 : x0) + bandPx * (i + 0.5);\n const label = fitLabel(item.label, Math.floor((horizontal ? x0 - pad : bandPx) / charPx));\n const v = item.value;\n const end = v === null ? zero : along(v);\n const lo = Math.min(zero, end);\n const hi = Math.max(zero, end);\n return (\n <g key={item.key ?? item.label} data-bar={item.label} data-value={v ?? undefined}>\n <title>{`${item.label}: ${v === null ? '\u2014' : withUnit(v)}`}</title>\n {v === null ? null : horizontal ? (\n <rect x={x0 + lo} y={centre - thick / 2} width={Math.max(1, hi - lo)} height={thick} style={{ fill: css('chart-1') }} />\n ) : (\n <rect x={centre - thick / 2} y={y1 - hi} width={thick} height={Math.max(1, hi - lo)} style={{ fill: css('chart-1') }} />\n )}\n {labelled && v !== null ? (\n horizontal ? (\n <text x={x0 + hi + pad} y={centre + font * 0.35} style={{ ...text, fill: css('foreground') }}>\n {withUnit(v)}\n </text>\n ) : (\n <text x={centre} y={y1 - hi - pad} textAnchor=\"middle\" style={{ ...text, fill: css('foreground') }}>\n {fmt(v)}\n </text>\n )\n ) : null}\n <text\n x={horizontal ? x0 - pad : centre}\n y={horizontal ? centre + font * 0.35 : y1 + font * 1.2}\n textAnchor={horizontal ? 'end' : 'middle'}\n style={{ ...text, ...muted }}\n >\n {label}\n </text>\n </g>\n );\n })}\n </svg>\n );\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// The few fixed words the aggregate and categorical widgets render themselves: the name of each\n// statistic, and the reasons they give instead of a figure. The widget packages carry no i18n\n// runtime, so this is the one small per-language table (modelled on level-strings.ts): a host that\n// adds a runtime later replaces exactly this file. Option labels are translated in the console's\n// catalogs, not here.\n\nimport { viewerLocale } from '@devicechain/widget-sdk';\n\nexport const AGGREGATE_STATS = ['avg', 'min', 'max', 'sum', 'count', 'last'] as const;\nexport type AggregateStat = (typeof AGGREGATE_STATS)[number];\n\nexport interface AggregateStrings {\n stats: Record<AggregateStat, string>;\n loading: string;\n // The data source cannot compute aggregates (a replayed recording, say).\n unsupported: string;\n failed: (message: string | undefined) => string;\n // Nothing was read in the span.\n noReadings: (span: string) => string;\n noData: string;\n // The widget's slot is bound to no device.\n unbound: string;\n // The accessible name of a card's trend strip: what it draws and over how long.\n trend: (stat: string, span: string) => string;\n // Categorical charts.\n noCategories: string;\n pieNegative: string;\n pieNoShare: string;\n}\n\nexport const AGGREGATE_STRINGS: Record<'en' | 'es', AggregateStrings> = {\n en: {\n stats: { avg: 'Average', min: 'Minimum', max: 'Maximum', sum: 'Sum', count: 'Readings', last: 'Latest' },\n loading: 'Loading',\n unsupported: 'This data source cannot compute aggregates',\n failed: (m) => (m ? `Aggregates unavailable: ${m}` : 'Aggregates unavailable'),\n noReadings: (span) => `No readings in the last ${span}`,\n noData: 'No data',\n unbound: 'Choose a device for this widget',\n trend: (stat, span) => `${stat} trend over the last ${span}`,\n noCategories: 'Select the measurements to show',\n pieNegative: 'A pie needs values of zero or more',\n pieNoShare: 'Every value is zero, so there is no share to show',\n },\n es: {\n stats: { avg: 'Promedio', min: 'M\u00EDnimo', max: 'M\u00E1ximo', sum: 'Suma', count: 'Lecturas', last: '\u00DAltimo' },\n loading: 'Cargando',\n unsupported: 'Esta fuente de datos no puede calcular agregados',\n failed: (m) => (m ? `Agregados no disponibles: ${m}` : 'Agregados no disponibles'),\n noReadings: (span) => `Sin lecturas en los \u00FAltimos ${span}`,\n noData: 'Sin datos',\n unbound: 'Elija un dispositivo para este widget',\n trend: (stat, span) => `Tendencia de ${stat.toLowerCase()} en los \u00FAltimos ${span}`,\n noCategories: 'Seleccione las mediciones que mostrar',\n pieNegative: 'Un gr\u00E1fico circular necesita valores de cero o m\u00E1s',\n pieNoShare: 'Todos los valores son cero, no hay proporci\u00F3n que mostrar',\n },\n};\n\nexport function aggregateStrings(locale: string | undefined = viewerLocale()): AggregateStrings {\n return locale?.toLowerCase().split('-')[0] === 'es' ? AGGREGATE_STRINGS.es : AGGREGATE_STRINGS.en;\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// The categories of a bar or pie chart, decided apart from the drawing: which measurements, in\n// what order, with which value. One category per measurement, valued by its latest reading.\n\nimport type { MeasurementStreamState, WidgetInstance } from '@devicechain/dashboards';\n\n// One category: its label and its value (null = no reading yet).\nexport interface CategoryItem {\n label: string;\n value: number | null;\n}\n\nexport const CATEGORY_SORTS = ['none', 'ascending', 'descending'] as const;\nexport type CategorySort = (typeof CATEGORY_SORTS)[number];\n\n// Unset or unknown keeps the datasource's order. (The widget reads its own option and passes it here.)\nexport function parseSort(v: unknown): CategorySort {\n return CATEGORY_SORTS.find((s) => s === v) ?? 'none';\n}\n\n// The measurements the datasource names, in the order it names them; with none named, every\n// measurement that has reported, in the order they were first seen. A category with no reading\n// yet is KEPT with a null value (a bar chart shows the label with no bar), so the chart does not\n// quietly drop a measurement that has not reported -- but it sorts after those that have.\nexport function categoryItems(\n widget: WidgetInstance,\n data: Pick<MeasurementStreamState, 'latest'>,\n sort: CategorySort,\n): CategoryItem[] {\n const named = widget.datasource?.measurements ?? [];\n const names = named.length > 0 ? [...new Set(named)] : Object.keys(data.latest);\n // A value that is not a finite number (NaN, an infinity) is no reading: it has no length and no\n // share, and drawing it as either would be a guess.\n const items = names.map((label) => {\n const v = data.latest[label]?.value;\n return { label, value: typeof v === 'number' && Number.isFinite(v) ? v : null };\n });\n if (sort === 'none') return items;\n const sign = sort === 'ascending' ? 1 : -1;\n return items\n .map((item, i) => ({ item, i }))\n .sort((a, b) => {\n if (a.item.value === null || b.item.value === null) {\n return a.item.value === b.item.value ? a.i - b.i : a.item.value === null ? 1 : -1;\n }\n return sign * (a.item.value - b.item.value) || a.i - b.i;\n })\n .map((x) => x.item);\n}\n\n// Why a pie cannot be drawn from these items, or undefined when it can. A pie is shares of a\n// whole: a negative value has no share, and all zeros have nothing to divide.\n// With no reading at all there is nothing to divide either, but that is `noData`, not a refusal.\nexport function pieProblem(items: readonly CategoryItem[]): 'negative' | 'noShare' | 'noData' | undefined {\n const values = items.map((i) => i.value).filter((v): v is number => v !== null);\n if (values.length === 0) return 'noData';\n if (values.some((v) => v < 0)) return 'negative';\n if (values.every((v) => v === 0)) return 'noShare';\n return undefined;\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// fleet-count -- the figure above a fleet: how many of a group's devices need attention (\"3 of 21\"),\n// or simply how many devices the group holds. It reads the same member feed as the fleet table, so\n// a board that already shows the table asks the platform for nothing more.\n//\n// - ATTENTION counts the devices whose newest reading of one measurement is in an abnormal band\n// of the card's thresholds (fleet-count-model.ts). The figure takes the colour of the worst\n// level among them; with none flagged (0 of N, or a fleet in its normal band) it keeps the\n// foreground, so a healthy fleet is calm;\n// - a device with no reading is counted apart (\"2 not reporting\", or \"1 could not be read\" when\n// its read failed), never as healthy; while any member is still loading there is no figure;\n// - a device whose profile does not declare the measurement is not in the \"of N\" at all, and is\n// noted apart (\"1 does not report this\"): a plant in a fuel count is outside the question,\n// which \"not reporting\" would misstate. A declared measurement the device is silent on (a dead\n// probe, a live heartbeat) stays in N as not reporting; so does a device whose profile could\n// not be read;\n// - a group larger than the feed reads says so (\"Showing 500 of 812 devices\") instead of\n// presenting the subset as the fleet;\n// - DEVICES shows the group's size, taken from the roster rather than from the readings.\n//\n// DOM text, so the readability gate sees it.\n\nimport { useMemo } from 'react';\n\nimport {\n css,\n FIT_MIN_FONT_PX,\n fontVar,\n levelColor,\n optString,\n optThresholds,\n primaryMeasurementName,\n spaceVar,\n useSeverityColors,\n WidgetFrame,\n type WidgetProps,\n} from '@devicechain/widget-sdk';\n\nimport { attentionCount } from '../fleet-count-model';\nimport { fleetStrings } from '../fleet-strings';\nimport { LevelEmpty, LevelReadout } from '../level-readout';\nimport { Explained } from '../no-reading';\n\n// The room the caption lines need, before the density scale.\nconst CAPTION_ROOM_PX = 22;\n\nexport function FleetCount({ widget, data }: WidgetProps) {\n const strings = fleetStrings();\n const severity = useSeverityColors();\n const fleet = data.members;\n const showDevices = optString(widget.options, 'show') === 'devices';\n const measurement = optString(widget.options, 'measurement') ?? primaryMeasurementName(widget);\n const thresholds = useMemo(() => optThresholds(widget.options, 'thresholds'), [widget.options]);\n const title = optString(widget.options, 'title');\n\n const empty = (message: string, kind?: 'unbound' | 'forbidden') => (\n <WidgetFrame title={title}>\n {kind ? <Explained kind={kind} message={message} /> : <LevelEmpty message={message} />}\n </WidgetFrame>\n );\n if (!fleet || fleet.status === 'loading') return empty(strings.loading);\n if (fleet.status === 'unbound') return empty(strings.unbound, 'unbound');\n if (fleet.status === 'forbidden') return empty(strings.forbidden, 'forbidden');\n if (fleet.status === 'unsupported') return empty(strings.unsupported);\n if (!showDevices) {\n if (measurement === undefined) return empty(strings.noMeasurementCount);\n if (thresholds.length === 0) return empty(strings.noBands);\n }\n\n const counted = showDevices ? undefined : attentionCount(fleet.members, measurement ?? '', thresholds);\n // Zero devices is a count, and the answer to \"how many\": 0. Attention over no devices has no\n // \"N\" to be a share of, so it says there are none, as the fleet table does.\n if (!showDevices && fleet.members.length === 0) return empty(strings.noMembers);\n // A count over the members read so far would be an undercount that looks like an answer.\n if (counted && counted.pending > 0) return empty(strings.loading);\n // No member's profile declares this measurement: there is no \"N\" here either. Over a\n // truncated group only the members read were asked, so the message says so rather than speaking\n // for devices past the page.\n if (counted && counted.members === 0) {\n return empty(fleet.truncated ? strings.noneMeasuredShown(fleet.members.length, fleet.total) : strings.noneMeasured);\n }\n\n const figure = showDevices ? String(fleet.total) : String(counted!.flagged);\n const unit = showDevices ? undefined : strings.countOf(counted!.members);\n const color = counted ? levelColor(severity, counted.worst) : undefined;\n const caption = showDevices ? strings.devices : counted!.flagged > 0 ? (counted!.label ?? strings.needAttention) : strings.needAttention;\n const notes: string[] = [];\n if (counted) {\n const silent = counted.members - counted.reporting - counted.unread;\n if (silent > 0) notes.push(strings.notReporting(silent));\n if (counted.unread > 0) notes.push(strings.notRead(counted.unread));\n if (counted.notMeasured > 0) notes.push(strings.notMeasured(counted.notMeasured));\n // The device count is the roster's own total, exact past the page; only the attention count\n // is taken from the members read, so only it says it covers part of the fleet.\n if (fleet.truncated) notes.push(strings.truncated(fleet.members.length, fleet.total));\n }\n\n return (\n <WidgetFrame title={title}>\n {({ size, scale }) => {\n const room = Math.round(CAPTION_ROOM_PX * scale) * (1 + notes.length);\n const fit = { width: size.width, height: Math.max(Math.round(FIT_MIN_FONT_PX * scale), size.height - room) };\n return (\n <div\n data-fleet-count={showDevices ? 'devices' : 'attention'}\n data-flagged={counted?.flagged}\n data-of={counted?.members}\n data-threshold-level={counted?.worst}\n style={{ display: 'flex', flexDirection: 'column', justifyContent: 'safe center', height: '100%', overflow: 'hidden' }}\n >\n <div style={{ flex: '1 1 0', minHeight: 0, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>\n <LevelReadout text={figure} unit={unit} color={color} size={fit} scale={scale} />\n </div>\n <div\n data-fleet-count-caption=\"\"\n style={{\n flex: '0 0 auto',\n marginTop: spaceVar(1),\n textAlign: 'center',\n fontSize: fontVar('sm'),\n color: css('muted-foreground'),\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n whiteSpace: 'nowrap',\n }}\n >\n {caption}\n </div>\n {notes.map((note) => (\n <div\n key={note}\n data-fleet-count-note=\"\"\n style={{ textAlign: 'center', fontSize: fontVar('xs'), color: css('muted-foreground'), overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}\n >\n {note}\n </div>\n ))}\n </div>\n );\n }}\n </WidgetFrame>\n );\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// What the fleet count card says, decided apart from the drawing: how many of a fleet's devices\n// are in an abnormal band of one measurement. Pure, so each rule is a test.\n//\n// - a device NEEDS ATTENTION when its newest reading of the measurement falls in a band of a\n// level that is not `normal` (below the lowest threshold is also not a band). The normal band\n// is the healthy state, so a fleet in it counts zero and the card stays neutral (ISA-101);\n// - a device with no reading of the measurement is neither healthy nor flagged: it is counted\n// apart, never folded into either figure -- as NOT REPORTING when its read settled and found\n// nothing (it sent nothing in the span, or sent others but not this one), and as NOT READ when the\n// read failed or did not load its newest reading (the device may well be reporting);\n// - a device whose profile does not DECLARE the measurement (a plant counted among the machines\n// in a fuel count) is outside the question, not silent: it is left out of the \"of N\" and\n// counted apart again (NOT MEASURED), so the card neither calls it \"not reporting\" nor drops it\n// unseen. That is decided from the declaration, never from what the device has sent: a fuel\n// sensor with a dead probe and a live heartbeat declares fuel and stays in N, as not reporting.\n// A device whose declaration is not known (not looked up yet, or the lookup failed) is taken to\n// declare it -- it is never left out on a guess;\n// - while any member's read is still loading there is no count yet: a figure over the members\n// read so far would undercount, and nothing on the card would say so;\n// - the card names the band when every flagged device is in the same labelled one (\"Low\"), and\n// says \"need attention\" otherwise.\n\nimport type { FleetMember } from '@devicechain/dashboards';\nimport { bandAt, SEVERITY_LEVELS, isAlarmLevel, type SeverityLevel, type Threshold } from '@devicechain/widget-sdk';\n\nimport { cellFor } from './fleet-model';\n\nexport interface AttentionCount {\n // The members the question covers: those the feed delivered (fewer than the fleet when it is\n // truncated), less the ones that do not report the measurement at all.\n members: number;\n // Members whose profile does not declare the measurement.\n notMeasured: number;\n flagged: number;\n // Members with a reading of the measurement.\n reporting: number;\n // Members whose read is still loading: while any is, there is no figure yet.\n pending: number;\n // Members whose read failed, or did not load their newest reading: not known to be silent.\n unread: number;\n // The worst level among the flagged, or undefined when none is.\n worst: SeverityLevel | undefined;\n // The shared band label of the flagged devices, when they all sit in one labelled band.\n label: string | undefined;\n}\n\nexport function attentionCount(members: readonly FleetMember[], measurement: string, thresholds: readonly Threshold[]): AttentionCount {\n let flagged = 0;\n let reporting = 0;\n let pending = 0;\n let unread = 0;\n let notMeasured = 0;\n let worst: SeverityLevel | undefined;\n let band: Threshold | undefined;\n let mixed = false;\n for (const m of members) {\n const cell = cellFor(m, measurement);\n if (cell.kind === 'loading') pending++;\n else if (cell.kind === 'failed' || cell.kind === 'unread') unread++;\n else if (cell.kind === 'not-reported') notMeasured++;\n if (cell.kind !== 'value') continue;\n reporting++;\n const hit = bandAt(thresholds, cell.value);\n if (!hit || !isAlarmLevel(hit.level)) continue;\n flagged++;\n if (worst === undefined || SEVERITY_LEVELS.indexOf(hit.level) > SEVERITY_LEVELS.indexOf(worst)) worst = hit.level;\n if (band === undefined) band = hit;\n else if (band !== hit) mixed = true;\n }\n return { members: members.length - notMeasured, notMeasured, flagged, reporting, pending, unread, worst, label: !mixed ? band?.label : undefined };\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// fleet-table -- one row per device of an anchor, one column per measurement. Each cell is that\n// DEVICE's own latest reading (the fleet feed keeps the device beside the value; the measurement\n// channel's name-only `latest` would collapse ten thermostats into one \"temperature\").\n//\n// - a band from the column's thresholds colours the value; the normal band keeps the table's own\n// colour, as everywhere else, so a table of healthy devices is calm;\n// - each cell shows the reading's age, and is called stale only against the configured\n// `staleAfterSeconds` (the freshness convention: no threshold, no stale mark). With\n// `ageLagSeconds` a cell shows its age only when its reading lags the NEWEST reading in the\n// table by at least that much (or is stale): on a fleet that reports every few seconds every\n// age reads \"a few seconds ago\", which is noise that doubles each row, and the frame's footer\n// already carries the fleet's own age. The lag is measured between readings, not against the\n// viewer's clock, so a played-back run (whose readings are all days old) shows the machine\n// that fell silent and no other;\n// - a cell with no number says WHY: still loading, the read failed, the device sent nothing in\n// the range, or its profile does not declare this measurement;\n// - columns sort from their headings (real buttons, so the keyboard works), the device column\n// drills like the alarm table's originator, and a long fleet is paged. A fleet beyond what the\n// resolver reads says \"Showing N of M devices\" rather than pass a subset off as the fleet;\n// - on a narrow board the table scrolls sideways inside its widget and keeps the device column\n// in view.\n\nimport type { FleetMember } from '@devicechain/dashboards';\nimport { useEffect, useMemo, useState, type CSSProperties, type ReactNode } from 'react';\n\nimport {\n ageSeconds,\n bandAt,\n css,\n fontVar,\n formatAge,\n formatValue,\n isStale,\n levelColor,\n optNumber,\n optBoolean,\n optString,\n parseThresholds,\n resolveStaleAfterSeconds,\n spaceVar,\n useSeverityColors,\n useWidgetScale,\n useWidgetSelect,\n useWidgetTimeRange,\n WidgetFrame,\n type Threshold,\n type WidgetProps,\n} from '@devicechain/widget-sdk';\n\nimport {\n DEFAULT_PAGE_SIZE,\n cellFor,\n clampPage,\n columnNames,\n deviceLabel,\n pageCount,\n sortMembers,\n type Cell,\n type SortKey,\n} from '../fleet-model';\nimport { fleetExport } from '../export-rows';\nimport { fleetStrings, type FleetStrings } from '../fleet-strings';\nimport { LevelEmpty } from '../level-readout';\nimport { Explained } from '../no-reading';\n\n// How often the ages are recomputed. Five seconds reads as live for \"12 sec. ago\" and costs a\n// page of cells one render per tick, not per second.\nconst AGE_TICK_MS = 5_000;\n\ninterface ColumnFormat {\n label?: string;\n unit?: string;\n precision?: number;\n}\n\nfunction isRecord(v: unknown): v is Record<string, unknown> {\n return typeof v === 'object' && v !== null && !Array.isArray(v);\n}\n\n// The per-column formatting, by measurement name. Read defensively: a hand-edited definition may\n// carry anything, and an entry that cannot be read drops out rather than take the table down.\nexport function columnFormats(raw: unknown): Map<string, ColumnFormat> {\n const out = new Map<string, ColumnFormat>();\n if (!Array.isArray(raw)) return out;\n for (const item of raw) {\n if (!isRecord(item) || typeof item.measurement !== 'string' || item.measurement === '') continue;\n const f: ColumnFormat = {};\n if (typeof item.label === 'string' && item.label !== '') f.label = item.label;\n if (typeof item.unit === 'string' && item.unit !== '') f.unit = item.unit;\n if (typeof item.precision === 'number' && Number.isInteger(item.precision) && item.precision >= 0 && item.precision <= 100) {\n f.precision = item.precision;\n }\n out.set(item.measurement, f);\n }\n return out;\n}\n\n// The bands of each column: the thresholds list, grouped by the measurement each entry names.\nexport function columnBands(raw: unknown): Map<string, Threshold[]> {\n const grouped = new Map<string, unknown[]>();\n if (Array.isArray(raw)) {\n for (const item of raw) {\n if (!isRecord(item) || typeof item.measurement !== 'string') continue;\n const list = grouped.get(item.measurement) ?? [];\n list.push(item);\n grouped.set(item.measurement, list);\n }\n }\n const out = new Map<string, Threshold[]>();\n for (const [name, items] of grouped) out.set(name, parseThresholds(items));\n return out;\n}\n\nconst cellBox: CSSProperties = { padding: `${spaceVar(1)} ${spaceVar(3)}`, textAlign: 'left', whiteSpace: 'nowrap', verticalAlign: 'top' };\nconst headBox: CSSProperties = {\n ...cellBox,\n fontSize: fontVar('xs'),\n fontWeight: 600,\n letterSpacing: '0.04em',\n color: css('muted-foreground'),\n borderBottom: `1px solid ${css('border')}`,\n background: css('card'),\n position: 'sticky',\n top: 0,\n zIndex: 1,\n};\n// The device column stays in view while the measurement columns scroll under it.\nconst stickyFirst: CSSProperties = { position: 'sticky', left: 0, background: css('card') };\nconst plainButton: CSSProperties = {\n padding: 0,\n border: 'none',\n background: 'none',\n font: 'inherit',\n color: 'inherit',\n cursor: 'pointer',\n textAlign: 'left',\n};\n\nexport function FleetTable({ widget, data }: WidgetProps) {\n const strings = fleetStrings();\n const fleet = data.members;\n const title = optString(widget.options, 'title');\n\n if (!fleet || fleet.status === 'loading') {\n return <Frame title={title}><LevelEmpty message={strings.loading} /></Frame>;\n }\n if (fleet.status === 'unbound') return <Frame title={title}><Explained kind=\"unbound\" message={strings.unbound} /></Frame>;\n if (fleet.status === 'forbidden') return <Frame title={title}><Explained kind=\"forbidden\" message={strings.forbidden} /></Frame>;\n if (fleet.status === 'unsupported') return <Frame title={title}><LevelEmpty message={strings.unsupported} /></Frame>;\n if (fleet.members.length === 0) return <Frame title={title}><LevelEmpty message={strings.noMembers} /></Frame>;\n return <Table widget={widget} fleet={fleet} strings={strings} />;\n}\n\n// Below this width (px of the widget body) the table becomes a card per device.\nexport const STACK_BELOW_PX = 480;\n\nfunction Responsive({ wide, narrow }: { wide: ReactNode; narrow: ReactNode }) {\n const { width } = useWidgetScale().size;\n // An unmeasured body (width 0: before the first layout, or under a test renderer) takes the table.\n return width > 0 && width < STACK_BELOW_PX ? narrow : wide;\n}\n\nfunction Frame({ title, children }: { title: string | undefined; children: ReactNode }) {\n return <WidgetFrame title={title} flush>{children}</WidgetFrame>;\n}\n\nfunction Table({\n widget,\n fleet,\n strings,\n}: {\n widget: WidgetProps['widget'];\n fleet: NonNullable<WidgetProps['data']['members']>;\n strings: FleetStrings;\n}) {\n const colors = useSeverityColors();\n const range = useWidgetTimeRange();\n const select = useWidgetSelect();\n const selectionTarget = optString(widget.options, 'selectionTarget');\n const drillTo = select && selectionTarget ? { select, slot: selectionTarget } : undefined;\n\n const columns = useMemo(() => columnNames(widget.datasource?.measurements ?? [], fleet.members), [widget.datasource, fleet.members]);\n const formats = useMemo(() => columnFormats(widget.options?.columns), [widget.options?.columns]);\n const bands = useMemo(() => columnBands(widget.options?.thresholds), [widget.options?.thresholds]);\n const staleAfter = resolveStaleAfterSeconds(widget.options);\n const ageLag = positiveSeconds(optNumber(widget.options, 'ageLagSeconds'));\n // The newest reading among the cells the table shows: what a cell's lag is measured from.\n const newest = useMemo(() => (ageLag === undefined ? undefined : newestShown(fleet.members, columns)), [ageLag, fleet.members, columns]);\n const pageSize = optNumber(widget.options, 'pageSize') ?? DEFAULT_PAGE_SIZE;\n\n // The author's order is the starting point; a click on a heading overrides it for this viewer.\n const authored: SortKey = { by: optString(widget.options, 'sortBy') || null, descending: optBoolean(widget.options, 'sortDescending') === true };\n const [picked, setPicked] = useState<SortKey | undefined>(undefined);\n const sort = picked ?? authored;\n const [page, setPage] = useState(0);\n\n // Ages move with the clock, and stand still at the freeze instant while the board is paused.\n const [tick, setTick] = useState(() => Date.now());\n useEffect(() => {\n const id = setInterval(() => setTick(Date.now()), AGE_TICK_MS);\n return () => clearInterval(id);\n }, []);\n const now = range?.paused ? range.pausedAt ?? tick : tick;\n\n const sorted = useMemo(() => sortMembers(fleet.members, sort), [fleet.members, sort.by, sort.descending]); // eslint-disable-line react-hooks/exhaustive-deps\n const shownPage = clampPage(page, sorted.length, pageSize);\n const rows = sorted.slice(shownPage * pageSize, (shownPage + 1) * pageSize);\n const pages = pageCount(sorted.length, pageSize);\n\n const sortOn = (by: string | null): void => {\n setPicked({ by, descending: sort.by === by ? !sort.descending : false });\n setPage(0);\n };\n const ariaSort = (by: string | null): 'ascending' | 'descending' | 'none' =>\n sort.by === by ? (sort.descending ? 'descending' : 'ascending') : 'none';\n const arrow = (by: string | null): string => (sort.by === by ? (sort.descending ? ' \u25BC' : ' \u25B2') : '');\n\n const wide = (\n <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: fontVar('md'), color: css('foreground') }}>\n <thead>\n <tr>\n <th scope=\"col\" aria-sort={ariaSort(null)} style={{ ...headBox, ...stickyFirst, zIndex: 2 }}>\n <button type=\"button\" style={plainButton} onClick={() => sortOn(null)} title={strings.sortBy(strings.device)}>\n {strings.device}\n {arrow(null)}\n </button>\n </th>\n {columns.map((name) => {\n const f = formats.get(name);\n const heading = (f?.label ?? name) + (f?.unit ? ` (${f.unit})` : '');\n return (\n <th key={name} scope=\"col\" aria-sort={ariaSort(name)} style={headBox}>\n <button type=\"button\" style={plainButton} onClick={() => sortOn(name)} title={strings.sortBy(heading)}>\n {heading}\n {arrow(name)}\n </button>\n </th>\n );\n })}\n </tr>\n </thead>\n <tbody>\n {rows.map((member) => {\n const label = deviceLabel(member);\n return (\n <tr key={member.token} data-device={member.token} style={{ borderBottom: `1px solid ${css('border')}` }}>\n <th scope=\"row\" style={{ ...cellBox, ...stickyFirst, fontWeight: 500 }}>\n {drillTo ? (\n <button\n type=\"button\"\n style={{ ...plainButton, color: css('primary'), textDecoration: 'underline', textUnderlineOffset: 2 }}\n title={strings.drill(label)}\n onClick={() => drillTo.select({ slot: drillTo.slot, binding: { kind: 'device', deviceToken: member.token } })}\n >\n {label}\n </button>\n ) : (\n label\n )}\n </th>\n {columns.map((name) => (\n <td key={name} data-measurement={name} style={cellBox}>\n <CellView\n cell={cellFor(member, name)}\n format={formats.get(name)}\n bands={bands.get(name)}\n colors={colors}\n staleAfter={staleAfter}\n ageLag={ageLag}\n newest={newest}\n now={now}\n strings={strings}\n />\n </td>\n ))}\n </tr>\n );\n })}\n </tbody>\n </table>\n );\n\n // Narrow, the table gives way to a card per device: a column cut off at the right edge is unread\n // on a wall display, where nobody scrolls sideways, so the narrow layout never overflows.\n const narrow = (\n <div data-fleet-stacked=\"\" style={{ color: css('foreground'), fontSize: fontVar('md') }}>\n <div style={{ display: 'flex', flexWrap: 'wrap', gap: spaceVar(2), padding: `${spaceVar(2)} ${spaceVar(3)}` }}>\n {[null, ...columns].map((by) => {\n const f = by === null ? undefined : formats.get(by);\n const heading = by === null ? strings.device : (f?.label ?? by);\n return (\n <button\n key={by ?? ''}\n type=\"button\"\n aria-pressed={sort.by === by}\n style={{ ...plainButton, fontSize: fontVar('xs'), fontWeight: 600, color: css('muted-foreground'), overflowWrap: 'anywhere' }}\n onClick={() => sortOn(by)}\n title={strings.sortBy(heading)}\n >\n {heading}\n {arrow(by)}\n </button>\n );\n })}\n </div>\n {rows.map((member) => {\n const label = deviceLabel(member);\n return (\n <div key={member.token} data-device={member.token} style={{ padding: `${spaceVar(2)} ${spaceVar(3)}`, borderTop: `1px solid ${css('border')}` }}>\n <div style={{ fontWeight: 500, overflowWrap: 'anywhere' }}>\n {drillTo ? (\n <button\n type=\"button\"\n style={{ ...plainButton, color: css('primary'), textDecoration: 'underline', textUnderlineOffset: 2, overflowWrap: 'anywhere' }}\n title={strings.drill(label)}\n onClick={() => drillTo.select({ slot: drillTo.slot, binding: { kind: 'device', deviceToken: member.token } })}\n >\n {label}\n </button>\n ) : (\n label\n )}\n </div>\n {columns.map((name) => {\n const f = formats.get(name);\n return (\n <div key={name} data-measurement={name} style={{ display: 'flex', justifyContent: 'space-between', gap: spaceVar(3), alignItems: 'baseline' }}>\n <span style={{ color: css('muted-foreground'), fontSize: fontVar('sm'), overflowWrap: 'anywhere' }}>{f?.label ?? name}</span>\n <span style={{ textAlign: 'right', overflowWrap: 'anywhere' }}>\n <CellView\n cell={cellFor(member, name)}\n format={f}\n bands={bands.get(name)}\n colors={colors}\n staleAfter={staleAfter}\n ageLag={ageLag}\n newest={newest}\n now={now}\n strings={strings}\n wrap\n />\n </span>\n </div>\n );\n })}\n </div>\n );\n })}\n </div>\n );\n\n return (\n <WidgetFrame\n title={optString(widget.options, 'title')}\n flush\n exportRows={() =>\n fleetExport({\n subject: optString(widget.options, 'title') ?? 'fleet',\n members: fleet.members,\n total: fleet.total,\n columns: columns.map((name) => {\n const f = formats.get(name);\n return { name, heading: (f?.label ?? name) + (f?.unit ? ` (${f.unit})` : ''), precision: f?.precision };\n }),\n sort,\n })\n }\n >\n <div style={{ display: 'flex', flexDirection: 'column', height: '100%', minHeight: 0 }}>\n {/* A focusable scroll region: a keyboard user can scroll a wide table sideways. */}\n <div\n role=\"region\"\n aria-label={optString(widget.options, 'title') ?? strings.device}\n tabIndex={0}\n data-fleet-scroll=\"\"\n style={{ flex: 1, minHeight: 0, overflow: 'auto' }}\n >\n <Responsive wide={wide} narrow={narrow} />\n </div>\n {(fleet.truncated || pages > 1) && (\n <div\n data-fleet-footer=\"\"\n style={{\n display: 'flex',\n flexWrap: 'wrap',\n alignItems: 'center',\n justifyContent: 'space-between',\n gap: spaceVar(2),\n padding: `${spaceVar(1)} ${spaceVar(3)}`,\n fontSize: fontVar('sm'),\n color: css('muted-foreground'),\n borderTop: `1px solid ${css('border')}`,\n }}\n >\n <span data-fleet-truncated={fleet.truncated ? '' : undefined}>\n {fleet.truncated ? strings.truncated(fleet.members.length, fleet.total) : ''}\n </span>\n {pages > 1 && (\n <span style={{ display: 'inline-flex', alignItems: 'center', gap: spaceVar(2) }}>\n <button type=\"button\" style={pagerButton} disabled={shownPage === 0} onClick={() => setPage(shownPage - 1)} aria-label={strings.previous}>\n \u2039\n </button>\n <span>{strings.range(shownPage * pageSize + 1, shownPage * pageSize + rows.length, sorted.length)}</span>\n <button type=\"button\" style={pagerButton} disabled={shownPage >= pages - 1} onClick={() => setPage(shownPage + 1)} aria-label={strings.next}>\n \u203A\n </button>\n </span>\n )}\n </div>\n )}\n </div>\n </WidgetFrame>\n );\n}\n\n// A seconds option that means something: a positive finite number, else unset.\nfunction positiveSeconds(v: number | undefined): number | undefined {\n return v !== undefined && Number.isFinite(v) && v > 0 ? v : undefined;\n}\n\n// The newest reading time (epoch ms) among the shown columns of every member, or undefined.\nexport function newestShown(members: readonly FleetMember[], columns: readonly string[]): number | undefined {\n let newest: number | undefined;\n for (const m of members) {\n for (const name of columns) {\n const t = m.latest[name]?.occurredTime;\n const at = t ? Date.parse(t) : Number.NaN;\n if (!Number.isNaN(at) && (newest === undefined || at > newest)) newest = at;\n }\n }\n return newest;\n}\n\nconst pagerButton: CSSProperties = {\n minWidth: '2em',\n padding: `0 ${spaceVar(2)}`,\n border: `1px solid ${css('border')}`,\n borderRadius: 4,\n background: 'none',\n color: 'inherit',\n font: 'inherit',\n cursor: 'pointer',\n};\n\nfunction CellView({\n cell,\n format,\n bands,\n colors,\n staleAfter,\n ageLag,\n newest,\n now,\n strings,\n wrap,\n}: {\n cell: Cell;\n format: ColumnFormat | undefined;\n bands: Threshold[] | undefined;\n colors: ReturnType<typeof useSeverityColors>;\n staleAfter: number | undefined;\n // Show the age only past this lag behind `newest` (seconds), or when stale; undefined = always.\n ageLag: number | undefined;\n newest: number | undefined;\n now: number;\n strings: FleetStrings;\n // Let the text break (the narrow card) instead of holding one line (the table cell).\n wrap?: boolean;\n}) {\n const muted: CSSProperties = { color: css('muted-foreground'), fontSize: fontVar('sm'), ...(wrap ? { overflowWrap: 'anywhere' as const } : {}) };\n switch (cell.kind) {\n case 'loading':\n return <span style={muted} title={strings.loading} aria-label={strings.loading}>\u2026</span>;\n case 'failed':\n return <span style={muted}>{strings.failed}</span>;\n case 'not-reported':\n return <span style={muted} data-cell-empty=\"not-reported\">{strings.notReported}</span>;\n case 'unread':\n return <span style={muted} data-cell-empty=\"unread\">{strings.unread}</span>;\n case 'no-data':\n return <span style={muted} data-cell-empty=\"no-data\">{strings.noData}</span>;\n case 'value': {\n const band = bands ? bandAt(bands, cell.value) : undefined;\n // The normal band has no colour: the cell keeps the table's own.\n const color = levelColor(colors, band?.level);\n const at = cell.sample.occurredTime ? Date.parse(cell.sample.occurredTime) : Number.NaN;\n const age = Number.isNaN(at) ? undefined : ageSeconds(at, now);\n const stale = age !== undefined && isStale(age, staleAfter);\n const showAge = age !== undefined && (ageLag === undefined || stale || newest === undefined || newest - at >= ageLag * 1000);\n return (\n <span data-stale={stale ? '' : undefined} style={{ display: 'inline-flex', flexDirection: 'column', opacity: stale ? 0.6 : 1 }}>\n <span data-threshold-level={band?.level} style={{ fontVariantNumeric: 'tabular-nums', color }}>\n {formatValue(cell.value, format?.precision)}\n {format?.unit ? ` ${format.unit}` : ''}\n </span>\n {age !== undefined && showAge && (\n <span style={muted} data-cell-age=\"\">\n {formatAge(age)}\n {stale ? ` \u00B7 ${strings.stale}` : ''}\n </span>\n )}\n </span>\n );\n }\n }\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// pie-chart -- each measurement's share of the total of their latest readings. A pie is shares of\n// one whole, so it is only drawn for values of zero or more that add to something: a negative\n// value, or all zeros, is an explained state, never a pie that quietly means something else.\n//\n// Drawn as SVG wedges with a DOM legend that names each category, its value and its share (so\n// every figure is on screen with no pointer to hover, and a category with no reading reads \"\u2014\",\n// not 0 %): no\n// chart type to register (see bar-chart.tsx), and the labels are text the readability gate sees.\n\nimport { useMemo } from 'react';\n\nimport { css, formatValue, optNumber, optString, fontVar, spaceVar, WidgetFrame, type WidgetProps } from '@devicechain/widget-sdk';\n\nimport { aggregateStrings } from '../aggregate-strings';\nimport { categoryItems, parseSort, pieProblem } from '../category-items';\nimport { pieSlices, wedgePath } from '../category-geometry';\nimport { LevelEmpty } from '../level-readout';\n\n// The chart ramp the wedges are drawn from, in order, repeating past five categories (the legend\n// names each one, so a repeated colour is still readable).\nconst RAMP = ['chart-1', 'chart-2', 'chart-3', 'chart-4', 'chart-5'] as const;\n\nexport function PieChart({ widget, data }: WidgetProps) {\n const sort = parseSort(optString(widget.options, 'sort'));\n const items = useMemo(() => categoryItems(widget, data, sort), [widget, data, sort]);\n const unit = optString(widget.options, 'unit');\n const precision = optNumber(widget.options, 'precision');\n const strings = aggregateStrings();\n const problem = items.length === 0 ? undefined : pieProblem(items);\n return (\n <WidgetFrame title={optString(widget.options, 'title')}>\n {items.length === 0 ? (\n <LevelEmpty message={strings.noCategories} />\n ) : problem ? (\n <LevelEmpty message={problem === 'negative' ? strings.pieNegative : problem === 'noShare' ? strings.pieNoShare : strings.noData} />\n ) : (\n <Pie items={items} unit={unit} precision={precision} />\n )}\n </WidgetFrame>\n );\n}\n\nfunction Pie({\n items,\n unit,\n precision,\n}: {\n items: { label: string; value: number | null }[];\n unit: string | undefined;\n precision: number | undefined;\n}) {\n const slices = pieSlices(items.map((i) => i.value));\n const fmt = (v: number) => `${formatValue(v, precision)}${unit ? ` ${unit}` : ''}`;\n const percent = (fraction: number) => `${Math.round(fraction * 100)}%`;\n return (\n <div style={{ display: 'flex', flexDirection: 'column', height: '100%', gap: spaceVar(1) }}>\n <svg\n data-pie=\"\"\n viewBox=\"0 0 100 100\"\n preserveAspectRatio=\"xMidYMid meet\"\n role=\"img\"\n aria-label={items.map((i, n) => `${i.label}: ${i.value === null ? '\u2014' : fmt(i.value)} (${percent(slices[n].fraction)})`).join(', ')}\n style={{ flex: '1 1 0', minHeight: 0, width: '100%', display: 'block' }}\n >\n {slices.map((s) => {\n const d = wedgePath(50, 50, 48, s.from, s.to);\n const item = items[s.index];\n return d ? (\n <path\n key={item.label}\n data-slice={item.label}\n data-fraction={s.fraction}\n d={d}\n style={{ fill: css(RAMP[s.index % RAMP.length]), stroke: css('card'), strokeWidth: 1 }}\n >\n <title>{`${item.label}: ${item.value === null ? '\u2014' : fmt(item.value)} (${percent(s.fraction)})`}</title>\n </path>\n ) : null;\n })}\n </svg>\n <ul\n data-pie-legend=\"\"\n style={{\n flex: '0 1 auto',\n maxHeight: '45%',\n overflow: 'hidden',\n margin: 0,\n padding: 0,\n listStyle: 'none',\n display: 'flex',\n flexWrap: 'wrap',\n justifyContent: 'center',\n gap: `0 ${spaceVar(3)}`,\n fontSize: fontVar('xs'),\n color: css('muted-foreground'),\n }}\n >\n {items.map((item, i) => (\n <li key={item.label} title={`${item.label}: ${item.value === null ? '\u2014' : fmt(item.value)}`} style={{ whiteSpace: 'nowrap' }}>\n <span\n aria-hidden=\"true\"\n style={{\n display: 'inline-block',\n width: '0.7em',\n height: '0.7em',\n marginRight: '0.4em',\n borderRadius: 2,\n background: css(RAMP[i % RAMP.length]),\n }}\n />\n {item.label} {item.value === null ? '\u2014' : `${fmt(item.value)} \u00B7 ${percent(slices[i].fraction)}`}\n </li>\n ))}\n </ul>\n </div>\n );\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// stat-card -- one statistic of one measurement over a span: its minimum, maximum, average, sum,\n// the number of readings, or its latest reading. The figure is the server's exact aggregate over\n// the span the board is set to (or the span this card is pinned to), and the card says which span\n// beside it. The figure takes the colour of the threshold band it falls in and keeps the\n// foreground in a `normal` band or below the lowest. DOM text, so the readability gate sees it.\n//\n// With `sparkline` set the card also draws the measurement's trend under the figure (sparkline.tsx):\n// neutral, one point per bucket of the span's chart, and left out where it would crowd the figure.\n//\n// Nothing here is derived from averages: a maximum is the largest reading, not the largest bucket\n// average. When the figure cannot be computed the card says why (aggregate-reading.ts).\n\nimport { useMemo } from 'react';\n\nimport { bucketSecondsFor, spanLabel, type AggregateStreamState } from '@devicechain/dashboards';\nimport {\n bandAt,\n css,\n FIT_MIN_FONT_PX,\n fontVar,\n formatValue,\n levelColor,\n optBoolean,\n optNumber,\n optString,\n optThresholds,\n pickSample,\n spaceVar,\n useSeverityColors,\n WidgetFrame,\n WidgetFreshnessProvider,\n type WidgetFreshness,\n type WidgetProps,\n} from '@devicechain/widget-sdk';\n\nimport { parseStat, statReading, type StatReading } from '../aggregate-reading';\nimport { aggregateStrings } from '../aggregate-strings';\nimport { LevelEmpty, LevelReadout } from '../level-readout';\nimport { NoReading } from '../no-reading';\nimport { Sparkline, trendPoints } from '../sparkline';\n\n// The room the caption under the figure needs, before the density scale.\nconst CAPTION_ROOM_PX = 22;\n// The strip a card's trend (`sparkline`) takes under the figure, before the density scale.\nconst SPARK_ROOM_PX = 28;\n// The share of the width the figure is fitted to when the caption sits beside it.\nconst BESIDE_FIGURE_SHARE = 0.6;\n\n// The latest reading is aged on screen like every other single reading (\"12 sec. ago\"), and with\n// no threshold of its own it is never called stale -- only its age is shown.\nconst AGE_ONLY: WidgetFreshness = { at: undefined, staleAfterSeconds: undefined };\n\nexport function StatCard({ widget, data }: WidgetProps<AggregateStreamState>) {\n const severity = useSeverityColors();\n const strings = aggregateStrings();\n const stat = parseStat(optString(widget.options, 'stat'));\n const reading = statReading(widget, data, stat);\n const thresholds = useMemo(() => optThresholds(widget.options, 'thresholds'), [widget.options]);\n const unit = optString(widget.options, 'unit');\n const precision = optNumber(widget.options, 'precision');\n const title = optString(widget.options, 'title') ?? reading.name;\n const wantsTrend = optBoolean(widget.options, 'sparkline');\n\n const card = (\n <WidgetFrame title={title} sampleTime={stat === 'last' ? (reading.kind === 'value' ? reading.time ?? null : null) : undefined}>\n {({ size, scale }) => {\n if (reading.kind === 'empty') {\n // Loading, unsupported and failed say what the source is doing; the rest is the absence of a\n // reading, explained once for every widget (NoReading).\n if (reading.reason === 'unbound' || reading.reason === 'noData' || reading.reason === 'noReadings') {\n return (\n <NoReading\n unbound={reading.reason === 'unbound'}\n measurement={reading.name}\n lastSample={pickSample(data.latest, reading.name)}\n unit={unit}\n precision={precision}\n detail={reading.reason === 'noReadings' ? emptyMessage(reading, strings) : undefined}\n // \"No readings in the span\" is the answer of a read that landed; \"no data\" for the\n // latest reading may be a board still loading, so it waits a moment first.\n waitMs={reading.reason === 'noReadings' ? 0 : undefined}\n />\n );\n }\n return <LevelEmpty message={emptyMessage(reading, strings)} />;\n }\n // A count is a number of readings: it has no unit, and no decimals.\n const isCount = reading.stat === 'count';\n const band = bandAt(thresholds, reading.value);\n const room = Math.round(CAPTION_ROOM_PX * scale);\n // The trend strip, when asked for and when it fits. It takes room from the figure only while\n // that leaves the figure two lines of the smallest readable size; otherwise it is left out\n // and the card is exactly the card without it.\n const floor2 = 2 * Math.round(FIT_MIN_FONT_PX * scale);\n const sparkHeight = Math.round(SPARK_ROOM_PX * scale);\n const points = wantsTrend && data.aggregates.status === 'ready' ? trendPoints(data.aggregates.series?.[reading.name ?? ''], reading.stat) : [];\n const showTrend = points.length > 0 && size.height - room - sparkHeight >= floor2;\n // The caption goes UNDER the figure while that leaves the figure two lines of the smallest\n // readable size; below that (the 2-row minimum with a subject line and a freshness footer,\n // at the smallest cell) it goes BESIDE it, so the declared minimum holds the whole card.\n const beside = size.height > 0 && size.height - room < 2 * Math.round(FIT_MIN_FONT_PX * scale);\n const figure = beside\n ? { width: Math.round(size.width * BESIDE_FIGURE_SHARE), height: size.height }\n : { width: size.width, height: Math.max(0, size.height - room - (showTrend ? sparkHeight : 0)) };\n return (\n <div\n data-stat={reading.stat}\n data-stat-span={reading.spanSeconds}\n data-stat-layout={beside ? 'beside' : 'under'}\n data-stat-trend={showTrend ? points.length : undefined}\n data-threshold-level={band?.level}\n title={band?.label}\n style={{\n display: 'flex',\n flexDirection: beside ? 'row' : 'column',\n alignItems: beside ? 'center' : undefined,\n justifyContent: 'safe center',\n gap: beside ? spaceVar(2) : undefined,\n height: '100%',\n overflow: 'hidden',\n }}\n >\n <div\n style={\n beside\n ? { flex: '0 1 auto', minWidth: 0, display: 'flex', alignItems: 'center' }\n : { flex: '1 1 0', minHeight: 0, display: 'flex', alignItems: 'center', justifyContent: 'center' }\n }\n >\n <LevelReadout\n text={formatValue(reading.value, isCount ? 0 : precision)}\n unit={isCount ? undefined : unit}\n color={levelColor(severity, band?.level)}\n size={figure}\n scale={scale}\n />\n </div>\n {showTrend && data.aggregates.status === 'ready' ? (\n <Sparkline\n points={points}\n height={sparkHeight}\n bucketSeconds={bucketSecondsFor(data.aggregates.spanSeconds)}\n // The latest reading has no per-bucket counterpart; its strip is the bucket averages.\n label={strings.trend(strings.stats[reading.stat === 'last' ? 'avg' : reading.stat], spanLabel(data.aggregates.spanSeconds))}\n />\n ) : null}\n <div\n data-stat-caption=\"\"\n style={{\n flex: beside ? '0 1 auto' : '0 0 auto',\n minWidth: 0,\n marginTop: beside ? undefined : spaceVar(1),\n textAlign: 'center',\n fontSize: fontVar('sm'),\n color: css('muted-foreground'),\n whiteSpace: 'nowrap',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n }}\n >\n {caption(reading, strings.stats)}\n </div>\n </div>\n );\n }}\n </WidgetFrame>\n );\n return stat === 'last' ? <WidgetFreshnessProvider freshness={AGE_ONLY}>{card}</WidgetFreshnessProvider> : card;\n}\n\n// \"Maximum \u00B7 1h\": the statistic and the span it covers. The latest reading covers no span.\nfunction caption(r: Extract<StatReading, { kind: 'value' }>, stats: Record<string, string>): string {\n const name = stats[r.stat];\n return r.spanSeconds === undefined ? name : `${name} \u00B7 ${spanLabel(r.spanSeconds)}`;\n}\n\nfunction emptyMessage(r: Extract<StatReading, { kind: 'empty' }>, strings: ReturnType<typeof aggregateStrings>): string {\n switch (r.reason) {\n case 'loading':\n return strings.loading;\n case 'unsupported':\n return strings.unsupported;\n case 'failed':\n return strings.failed(r.message);\n case 'noReadings':\n return strings.noReadings(r.spanSeconds === undefined ? '' : spanLabel(r.spanSeconds));\n case 'noData':\n return strings.noData;\n case 'unbound':\n return strings.unbound;\n }\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// What a stat widget shows, decided once and apart from the drawing: one figure, or the reason\n// there is none. Every path that cannot produce a real figure says so -- nothing here falls back\n// to a neighbouring statistic or to a number it was not given.\n\nimport type { AggregateStreamState, WidgetInstance } from '@devicechain/dashboards';\nimport { pickSample, primaryMeasurementName } from '@devicechain/widget-sdk';\n\nimport { AGGREGATE_STATS, type AggregateStat } from './aggregate-strings';\n\nexport type EmptyReason = 'loading' | 'unsupported' | 'failed' | 'noReadings' | 'noData' | 'unbound';\n\nexport type StatReading =\n | {\n kind: 'value';\n stat: AggregateStat;\n name: string | undefined;\n value: number;\n // The span the figure covers, in seconds; undefined for the latest reading, which has none.\n spanSeconds: number | undefined;\n // The latest reading's own time, which the card ages on screen; undefined for an aggregate.\n time?: string | null;\n }\n | { kind: 'empty'; reason: EmptyReason; name: string | undefined; message?: string; spanSeconds?: number };\n\n// The statistic a widget is set to. Unset or unknown is the average, the one a card is added with.\n// (The widget reads its own option and passes it here: option reads live in the widget's file.)\nexport function parseStat(v: unknown): AggregateStat {\n return AGGREGATE_STATS.find((s) => s === v) ?? 'avg';\n}\n\nexport function statReading(widget: WidgetInstance, data: AggregateStreamState, stat: AggregateStat): StatReading {\n const { aggregates } = data;\n const pinned = primaryMeasurementName(widget);\n // A slot nothing is bound to names no device, so it has no figure -- not even a count of zero,\n // which would say a device was asked and sent nothing. It says so, as the fleet widgets do.\n if (widget.datasource?.kind === 'unbound') return { kind: 'empty', reason: 'unbound', name: pinned };\n\n // The latest reading is a reading, not an aggregate: it needs no aggregate read, and works\n // where the source cannot compute one. When the read did land, a reading older than the span is\n // not \"the latest in the span\", so it is refused rather than shown.\n if (stat === 'last') {\n const sample = pickSample(data.latest, pinned);\n const name = pinned ?? sample?.name;\n if (sample?.value == null) return { kind: 'empty', reason: 'noData', name };\n if (aggregates.status === 'ready' && sample.occurredTime && Date.parse(sample.occurredTime) < Date.parse(aggregates.startTime)) {\n return { kind: 'empty', reason: 'noReadings', name, spanSeconds: aggregates.spanSeconds };\n }\n return { kind: 'value', stat, name, value: sample.value, spanSeconds: undefined, time: sample.occurredTime };\n }\n\n if (aggregates.status === 'loading') return { kind: 'empty', reason: 'loading', name: pinned };\n if (aggregates.status === 'unavailable') {\n return { kind: 'empty', reason: aggregates.reason, name: pinned, message: aggregates.message };\n }\n const name = pinned ?? Object.keys(aggregates.byName)[0];\n const a = name === undefined ? undefined : aggregates.byName[name];\n if (!a) {\n // The number of readings in a span that holds none is a number: zero, which is the figure a\n // \"no readings in the last hour\" tile exists to show. It needs a measurement the card is set\n // to and a device it was read from (an unbound slot was answered above); every other statistic\n // of no readings is undefined.\n if (stat === 'count' && pinned !== undefined && widget.datasource) {\n return { kind: 'value', stat, name: pinned, value: 0, spanSeconds: aggregates.spanSeconds };\n }\n return { kind: 'empty', reason: 'noReadings', name, spanSeconds: aggregates.spanSeconds };\n }\n return { kind: 'value', stat, name, value: a[stat], spanSeconds: aggregates.spanSeconds };\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// The trend strip under a stat card's figure: the card's own buckets drawn as one line, so \"is it\n// rising?\" is answered on the card itself. A few lines of SVG, no chart engine, so it adds\n// nothing to the lazy chunk it lives in beyond itself.\n//\n// - it is NEUTRAL: the stroke is the muted foreground, never a threshold colour. The figure\n// carries the band; a trend that turned red as well would say the same thing twice, and a\n// healthy card stays calm (ISA-101);\n// - the x axis is time, not index, and the line breaks where a stretch of buckets is missing, so a\n// device that went silent shows a gap rather than a line drawn across it;\n// - a lone point is a dot, and no point draws nothing (the figure beside it says why);\n// - each point is the bucket's own aggregate for the statistic the card shows. The latest\n// reading has no bucket aggregate, so its strip is the bucket averages: a trend, never a reading.\n\nimport type { AggregateTrendPoint } from '@devicechain/dashboards';\nimport { css } from '@devicechain/widget-sdk';\n\nimport type { AggregateStat } from './aggregate-strings';\n\nexport interface TrendPoint {\n at: number;\n value: number;\n}\n\n// The value a bucket contributes for the statistic the card shows.\nexport function trendValue(point: AggregateTrendPoint, stat: AggregateStat): number {\n switch (stat) {\n case 'min':\n return point.min;\n case 'max':\n return point.max;\n case 'sum':\n return point.sum;\n case 'count':\n return point.count;\n case 'avg':\n case 'last':\n return point.avg;\n }\n}\n\nexport function trendPoints(series: readonly AggregateTrendPoint[] | undefined, stat: AggregateStat): TrendPoint[] {\n if (!series) return [];\n const out: TrendPoint[] = [];\n for (const p of series) {\n const at = Date.parse(p.bucketStart);\n const value = trendValue(p, stat);\n if (Number.isFinite(at) && Number.isFinite(value)) out.push({ at, value });\n }\n return out.sort((a, b) => a.at - b.at);\n}\n\nconst VIEW_WIDTH = 100;\nconst PAD = 3;\n\nexport interface SparklineGeometry {\n // One \"M x y L x y ...\" sub-path per run of consecutive buckets.\n d: string;\n // The newest point as a percentage of the strip, for the end dot.\n end: { left: number; top: number } | undefined;\n points: number;\n}\n\n// sparklineGeometry maps the points into a VIEW_WIDTH x height box. A gap -- a stretch several times\n// longer than the series' usual step -- starts a new run.\nexport function sparklineGeometry(points: readonly TrendPoint[], height: number, bucketSeconds: number): SparklineGeometry {\n if (points.length === 0) return { d: '', end: undefined, points: 0 };\n const t0 = points[0].at;\n const t1 = points[points.length - 1].at;\n let lo = Infinity;\n let hi = -Infinity;\n for (const p of points) {\n lo = Math.min(lo, p.value);\n hi = Math.max(hi, p.value);\n }\n const x = (at: number): number => (t1 === t0 ? VIEW_WIDTH / 2 : PAD + ((at - t0) / (t1 - t0)) * (VIEW_WIDTH - 2 * PAD));\n // A flat series sits mid-strip: there is nothing to scale, and the bottom edge would read as \"low\".\n const y = (v: number): number => (hi === lo ? height / 2 : PAD + (1 - (v - lo) / (hi - lo)) * (height - 2 * PAD));\n // A device that reports every five minutes into one-minute buckets has a point every five\n // buckets, and that is a line, not five gaps. A gap is a stretch several times longer than the\n // series' own usual step (and never under two buckets).\n const steps = points.slice(1).map((p, i) => p.at - points[i].at).sort((a, b) => a - b);\n const usual = steps.length > 0 ? steps[Math.floor(steps.length / 2)] : 0;\n const gap = Math.max(bucketSeconds * 2000, usual * 3);\n let d = '';\n let prev: number | undefined;\n for (const p of points) {\n const cmd = prev === undefined || p.at - prev > gap ? 'M' : 'L';\n d += `${d ? ' ' : ''}${cmd}${x(p.at).toFixed(2)} ${y(p.value).toFixed(2)}`;\n prev = p.at;\n }\n const last = points[points.length - 1];\n return { d, end: { left: (x(last.at) / VIEW_WIDTH) * 100, top: (y(last.value) / height) * 100 }, points: points.length };\n}\n\nexport function Sparkline({\n points,\n height,\n bucketSeconds,\n label,\n}: {\n points: readonly TrendPoint[];\n height: number;\n bucketSeconds: number;\n label: string;\n}) {\n const g = sparklineGeometry(points, height, bucketSeconds);\n if (g.points === 0) return null;\n return (\n <div data-sparkline={g.points} role=\"img\" aria-label={label} title={label} style={{ position: 'relative', height, width: '100%', flex: '0 0 auto' }}>\n <svg viewBox={`0 0 ${VIEW_WIDTH} ${height}`} preserveAspectRatio=\"none\" width=\"100%\" height={height} style={{ display: 'block', overflow: 'visible' }} aria-hidden=\"true\">\n {g.points > 1 ? (\n <path d={g.d} fill=\"none\" stroke={css('muted-foreground')} strokeWidth={1.5} strokeLinejoin=\"round\" strokeLinecap=\"round\" vectorEffect=\"non-scaling-stroke\" />\n ) : null}\n </svg>\n {g.end ? (\n <span\n data-sparkline-end=\"\"\n style={{\n position: 'absolute',\n left: `${g.end.left}%`,\n top: `${g.end.top}%`,\n width: 6,\n height: 6,\n marginLeft: -3,\n marginTop: -3,\n borderRadius: '50%',\n background: css('muted-foreground'),\n }}\n />\n ) : null}\n </div>\n );\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["useMemo", "css", "optString", "WidgetFrame", "jsx", "jsxs", "optString", "useMemo", "WidgetFrame", "caption", "css", "useMemo", "bandAt", "css", "fontVar", "formatValue", "levelColor", "optNumber", "optString", "spaceVar", "useSeverityColors", "WidgetFrame", "jsx", "jsxs", "spaceVar", "fontVar", "css", "optString", "WidgetFrame", "useSeverityColors", "useMemo", "optNumber", "bandAt", "levelColor", "formatValue", "useMemo", "css", "formatValue", "optNumber", "optString", "fontVar", "spaceVar", "WidgetFrame", "jsx", "jsxs", "optString", "useMemo", "optNumber", "WidgetFrame", "formatValue", "spaceVar", "css", "fontVar", "useMemo", "bandAt", "css", "FIT_MIN_FONT_PX", "fontVar", "formatValue", "levelColor", "optBoolean", "optNumber", "optString", "optThresholds", "pickSample", "spaceVar", "useSeverityColors", "WidgetFrame", "primaryMeasurementName", "primaryMeasurementName", "name", "css", "jsx", "jsxs", "jsx", "jsxs", "CAPTION_ROOM_PX", "useSeverityColors", "optString", "useMemo", "optThresholds", "optNumber", "optBoolean", "WidgetFrame", "pickSample", "bandAt", "FIT_MIN_FONT_PX", "spaceVar", "formatValue", "levelColor", "fontVar", "css"]
|
|
7
|
+
}
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
NoReading
|
|
3
|
+
} from "./chunk-JVC6JYUR.js";
|
|
4
|
+
|
|
1
5
|
// src/widgets/table.tsx
|
|
2
6
|
import {
|
|
3
7
|
css,
|
|
@@ -27,13 +31,17 @@ function Table({ widget, data }) {
|
|
|
27
31
|
const rows = Object.values(data.latest).sort((a, b) => a.name.localeCompare(b.name));
|
|
28
32
|
const precision = optNumber(widget.options, "precision");
|
|
29
33
|
const flash = optBoolean(widget.options, "flashOnChange");
|
|
34
|
+
if (rows.length === 0) {
|
|
35
|
+
const asked = widget.datasource?.measurements;
|
|
36
|
+
return /* @__PURE__ */ jsx(WidgetFrame, { title: optString(widget.options, "title"), children: /* @__PURE__ */ jsx(NoReading, { unbound: widget.datasource?.kind === "unbound", measurement: asked?.length === 1 ? asked[0] : void 0, precision }) });
|
|
37
|
+
}
|
|
30
38
|
return /* @__PURE__ */ jsx(WidgetFrame, { title: optString(widget.options, "title"), flush: true, children: /* @__PURE__ */ jsx("div", { style: { height: "100%", overflow: "auto" }, children: /* @__PURE__ */ jsxs("table", { style: { width: "100%", borderCollapse: "collapse", fontSize: fontVar("md"), color: css("foreground") }, children: [
|
|
31
39
|
/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
|
|
32
40
|
/* @__PURE__ */ jsx("th", { style: headCell, children: "Measurement" }),
|
|
33
41
|
/* @__PURE__ */ jsx("th", { style: headCell, children: "Value" }),
|
|
34
42
|
/* @__PURE__ */ jsx("th", { style: headCell, children: "Time" })
|
|
35
43
|
] }) }),
|
|
36
|
-
/* @__PURE__ */ jsx("tbody", { children: rows.
|
|
44
|
+
/* @__PURE__ */ jsx("tbody", { children: rows.map((row) => /* @__PURE__ */ jsxs("tr", { children: [
|
|
37
45
|
/* @__PURE__ */ jsx("td", { style: cell, children: row.name }),
|
|
38
46
|
/* @__PURE__ */ jsx("td", { style: { ...cell, fontVariantNumeric: "tabular-nums" }, children: /* @__PURE__ */ jsx(
|
|
39
47
|
FlashValue,
|
|
@@ -52,4 +60,4 @@ function Table({ widget, data }) {
|
|
|
52
60
|
export {
|
|
53
61
|
Table
|
|
54
62
|
};
|
|
55
|
-
//# sourceMappingURL=chunk-
|
|
63
|
+
//# sourceMappingURL=chunk-7BUCJQFP.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/widgets/table.tsx"],
|
|
4
|
+
"sourcesContent": ["// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// table \u2014 the latest value of every measurement the datasource carries, one row\n// per measurement name.\n\nimport type { CSSProperties } from 'react';\n\nimport {\n css,\n fontVar,\n spaceVar,\n FlashValue,\n formatTimestamp,\n optBoolean,\n optNumber,\n optString,\n useWidgetTimeZone,\n WidgetFrame,\n type WidgetProps,\n} from '@devicechain/widget-sdk';\n\nimport { NoReading } from '../no-reading-core';\n\nconst cell: CSSProperties = { padding: `${spaceVar(1)} ${spaceVar(3)}`, textAlign: 'left' };\nconst headCell: CSSProperties = {\n ...cell,\n fontSize: fontVar('xs'),\n fontWeight: 600,\n textTransform: 'uppercase',\n letterSpacing: '0.04em',\n color: css('muted-foreground'),\n borderBottom: `1px solid ${css('border')}`,\n};\n\nexport function Table({ widget, data }: WidgetProps) {\n const timeZone = useWidgetTimeZone();\n const rows = Object.values(data.latest).sort((a, b) => a.name.localeCompare(b.name));\n // Round to a fixed number of decimals when configured; unset keeps the raw value\n // (formatValue's default). A live sensor value is otherwise a wall of digits.\n const precision = optNumber(widget.options, 'precision');\n // Opt-in directional flash per row (keyed by measurement name, so each value's cue is\n // independent \u2014 see FlashValue).\n const flash = optBoolean(widget.options, 'flashOnChange');\n\n if (rows.length === 0) {\n const asked = widget.datasource?.measurements;\n return (\n <WidgetFrame title={optString(widget.options, 'title')}>\n <NoReading unbound={widget.datasource?.kind === 'unbound'} measurement={asked?.length === 1 ? asked[0] : undefined} precision={precision} />\n </WidgetFrame>\n );\n }\n\n return (\n <WidgetFrame title={optString(widget.options, 'title')} flush>\n <div style={{ height: '100%', overflow: 'auto' }}>\n <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: fontVar('md'), color: css('foreground') }}>\n <thead>\n <tr>\n <th style={headCell}>Measurement</th>\n <th style={headCell}>Value</th>\n <th style={headCell}>Time</th>\n </tr>\n </thead>\n <tbody>\n {rows.map((row) => (\n <tr key={row.name}>\n <td style={cell}>{row.name}</td>\n <td style={{ ...cell, fontVariantNumeric: 'tabular-nums' }}>\n <FlashValue\n value={row.value}\n precision={precision}\n enabled={flash}\n identity={`${row.deviceToken}:${row.name}`}\n />\n {/* classifier is deliberately NOT shown: it is the bound metric\n definition's internal id (ADR-016), not a user-facing label \u2014\n appending it rendered as spurious digits after the value. */}\n </td>\n <td style={{ ...cell, color: css('muted-foreground') }}>{formatTimestamp(row.occurredTime, timeZone)}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n </WidgetFrame>\n );\n}\n"],
|
|
5
|
+
"mappings": ";;;;;AAQA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AA6BC,cAUI,YAVJ;AAzBR,IAAM,OAAsB,EAAE,SAAS,GAAG,SAAS,CAAC,CAAC,IAAI,SAAS,CAAC,CAAC,IAAI,WAAW,OAAO;AAC1F,IAAM,WAA0B;AAAA,EAC9B,GAAG;AAAA,EACH,UAAU,QAAQ,IAAI;AAAA,EACtB,YAAY;AAAA,EACZ,eAAe;AAAA,EACf,eAAe;AAAA,EACf,OAAO,IAAI,kBAAkB;AAAA,EAC7B,cAAc,aAAa,IAAI,QAAQ,CAAC;AAC1C;AAEO,SAAS,MAAM,EAAE,QAAQ,KAAK,GAAgB;AACnD,QAAM,WAAW,kBAAkB;AACnC,QAAM,OAAO,OAAO,OAAO,KAAK,MAAM,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,IAAI,CAAC;AAGnF,QAAM,YAAY,UAAU,OAAO,SAAS,WAAW;AAGvD,QAAM,QAAQ,WAAW,OAAO,SAAS,eAAe;AAExD,MAAI,KAAK,WAAW,GAAG;AACrB,UAAM,QAAQ,OAAO,YAAY;AACjC,WACE,oBAAC,eAAY,OAAO,UAAU,OAAO,SAAS,OAAO,GACnD,8BAAC,aAAU,SAAS,OAAO,YAAY,SAAS,WAAW,aAAa,OAAO,WAAW,IAAI,MAAM,CAAC,IAAI,QAAW,WAAsB,GAC5I;AAAA,EAEJ;AAEA,SACE,oBAAC,eAAY,OAAO,UAAU,OAAO,SAAS,OAAO,GAAG,OAAK,MAC3D,8BAAC,SAAI,OAAO,EAAE,QAAQ,QAAQ,UAAU,OAAO,GAC7C,+BAAC,WAAM,OAAO,EAAE,OAAO,QAAQ,gBAAgB,YAAY,UAAU,QAAQ,IAAI,GAAG,OAAO,IAAI,YAAY,EAAE,GAC3G;AAAA,wBAAC,WACC,+BAAC,QACC;AAAA,0BAAC,QAAG,OAAO,UAAU,yBAAW;AAAA,MAChC,oBAAC,QAAG,OAAO,UAAU,mBAAK;AAAA,MAC1B,oBAAC,QAAG,OAAO,UAAU,kBAAI;AAAA,OAC3B,GACF;AAAA,IACA,oBAAC,WACE,eAAK,IAAI,CAAC,QACP,qBAAC,QACC;AAAA,0BAAC,QAAG,OAAO,MAAO,cAAI,MAAK;AAAA,MAC3B,oBAAC,QAAG,OAAO,EAAE,GAAG,MAAM,oBAAoB,eAAe,GACvD;AAAA,QAAC;AAAA;AAAA,UACC,OAAO,IAAI;AAAA,UACX;AAAA,UACA,SAAS;AAAA,UACT,UAAU,GAAG,IAAI,WAAW,IAAI,IAAI,IAAI;AAAA;AAAA,MAC1C,GAIF;AAAA,MACA,oBAAC,QAAG,OAAO,EAAE,GAAG,MAAM,OAAO,IAAI,kBAAkB,EAAE,GAAI,0BAAgB,IAAI,cAAc,QAAQ,GAAE;AAAA,SAb9F,IAAI,IAcb,CACD,GACL;AAAA,KACF,GACF,GACF;AAEJ;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import {
|
|
2
|
+
NoReading
|
|
3
|
+
} from "./chunk-JVC6JYUR.js";
|
|
4
|
+
import {
|
|
5
|
+
LevelEmpty
|
|
6
|
+
} from "./chunk-EQOWBDS7.js";
|
|
7
|
+
|
|
8
|
+
// src/no-reading.tsx
|
|
9
|
+
import { WidgetEmptyState } from "@devicechain/widget-sdk/empty-state";
|
|
10
|
+
import { jsx } from "react/jsx-runtime";
|
|
11
|
+
function Explained({ kind, message }) {
|
|
12
|
+
return /* @__PURE__ */ jsx(WidgetEmptyState, { kind, detail: message });
|
|
13
|
+
}
|
|
14
|
+
function LevelEmptyFor({ reading, reason }) {
|
|
15
|
+
if (reason !== void 0) return /* @__PURE__ */ jsx(LevelEmpty, { message: reason });
|
|
16
|
+
return /* @__PURE__ */ jsx(NoReading, { unbound: reading.unbound, measurement: reading.name, lastSample: reading.sample, unit: reading.unit, precision: reading.precision });
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export {
|
|
20
|
+
Explained,
|
|
21
|
+
LevelEmptyFor
|
|
22
|
+
};
|
|
23
|
+
//# sourceMappingURL=chunk-AF4M5OXQ.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/no-reading.tsx"],
|
|
4
|
+
"sourcesContent": ["// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// The explained empty state as the level and fleet widgets reach it; the single-device decision\n// itself is no-reading-core.tsx.\n\nimport { WidgetEmptyState } from '@devicechain/widget-sdk/empty-state';\n\nimport type { LevelReading } from './level-reading';\nimport { LevelEmpty } from './level-readout';\nimport { NoReading } from './no-reading-core';\n\nexport { ASK_DELAY_MS, SETTLE_MS, emptyKindFor, lastReadingHint, NoReading, type NoReadingProps } from './no-reading-core';\n\n// A whole-widget state the platform or the binding decides (a refusal, an unchosen device or group),\n// in the shared look. The widget's own sentence stays as the explanation, so a fleet widget still\n// says \"Choose a device group\", not the single-device wording.\nexport function Explained({ kind, message }: { kind: 'unbound' | 'forbidden'; message: string }) {\n return <WidgetEmptyState kind={kind} detail={message} />;\n}\n\n// The empty state of a level widget: its own configuration problem when it has one (the author's fix\n// comes first), else the explained absence of a reading.\nexport function LevelEmptyFor({ reading, reason }: { reading: LevelReading; reason: string | undefined }) {\n if (reason !== undefined) return <LevelEmpty message={reason} />;\n return <NoReading unbound={reading.unbound} measurement={reading.name} lastSample={reading.sample} unit={reading.unit} precision={reading.precision} />;\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;AAMA,SAAS,wBAAwB;AAYxB;AADF,SAAS,UAAU,EAAE,MAAM,QAAQ,GAAuD;AAC/F,SAAO,oBAAC,oBAAiB,MAAY,QAAQ,SAAS;AACxD;AAIO,SAAS,cAAc,EAAE,SAAS,OAAO,GAA0D;AACxG,MAAI,WAAW,OAAW,QAAO,oBAAC,cAAW,SAAS,QAAQ;AAC9D,SAAO,oBAAC,aAAU,SAAS,QAAQ,SAAS,aAAa,QAAQ,MAAM,YAAY,QAAQ,QAAQ,MAAM,QAAQ,MAAM,WAAW,QAAQ,WAAW;AACvJ;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/level-readout.tsx"],
|
|
4
|
-
"sourcesContent": ["// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// The fitted DOM readout and the explained empty state the level-and-indicator widgets share.\n// DOM, not canvas text, so the readability gate can measure it.\n\nimport {\n css,\n fitValueFont,\n fitValueFontCss,\n FIT_MIN_FONT_PX,\n fontVar,\n spaceVar,\n useStickyChars,\n} from '@devicechain/widget-sdk';\n\nimport type { LevelStrings } from './level-strings';\nimport type { ScalePosition } from './value-scale';\n\n// The room the readout may fill (a box inside the frame's content column) and the density scale.\nexport interface ReadoutRoom {\n size: { width: number; height: number };\n scale: number;\n}\n\n// The words the off-scale marker is labelled with; the only strings the readout itself reads.\nexport type MarkerStrings = Pick<LevelStrings, 'aboveScale' | 'belowScale'>;\n\n// A readout that can be marked off-scale must be handed the marker's label, so an unlabelled\n// marker cannot be written; one that is never marked (the gauge) needs no strings at all, which\n// keeps the level-strings table out of an eager widget's bundle.\nexport type LevelReadoutProps = ReadoutRoom & {\n text: string;\n unit?: string;\n color?: string;\n} & (\n | {\n // Set when the value lies off the scale: a marker is drawn beside it.\n position?: ScalePosition;\n strings: MarkerStrings;\n }\n | { position?: undefined; strings?: undefined }\n );\n\nconst MARKER = { above: '\u25B2', below: '\u25BC' } as const;\n\nexport function LevelReadout({ text, unit, color, position, size, scale, strings }: LevelReadoutProps) {\n const hasUnit = unit !== undefined && unit !== '';\n const marked = position === 'above' || position === 'below';\n // The unit and the marker render at half size, so each counts for half its characters.\n const chars = useStickyChars(text.length + (hasUnit ? Math.ceil(unit.length / 2) + 1 : 0) + (marked ? 1 : 0));\n const floor = Math.round(FIT_MIN_FONT_PX * scale);\n const measured = size.width > 0 && size.height > 0;\n const px = measured ? fitValueFont(size, chars, { floor }) : undefined;\n const font = px ?? fitValueFontCss(chars, { floor });\n const half = px !== undefined ? Math.round(px / 2) : `calc(${font} / 2)`;\n return (\n <div\n data-level-readout=\"\"\n style={{\n fontSize: font,\n fontWeight: 600,\n lineHeight: 1.1,\n whiteSpace: 'nowrap',\n fontVariantNumeric: 'tabular-nums',\n color: color ?? css('foreground'),\n textAlign: 'center',\n }}\n >\n {text}\n {hasUnit ? (\n <span style={{ fontSize: half, color: css('muted-foreground'), marginLeft: spaceVar(1) }}>{unit}</span>\n ) : null}\n {marked ? (\n <span\n role=\"img\"\n aria-label={position === 'above' ? strings.aboveScale : strings.belowScale}\n title={position === 'above' ? strings.aboveScale : strings.belowScale}\n style={{ fontSize: half, color: css('muted-foreground'), marginLeft: spaceVar(1) }}\n >\n {MARKER[position]}\n </span>\n ) : null}\n </div>\n );\n}\n\n// Why there is nothing to draw: a short sentence in the widget's own box, never a fake value.\nexport function LevelEmpty({ message }: { message: string }) {\n return (\n <div\n data-level-empty=\"\"\n style={{\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n height: '100%',\n textAlign: 'center',\n color: css('muted-foreground'),\n fontSize: fontVar('sm'),\n overflowWrap: 'anywhere',\n }}\n >\n {message}\n </div>\n );\n}\n"],
|
|
4
|
+
"sourcesContent": ["// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// The fitted DOM readout and the explained empty state the level-and-indicator widgets share.\n// DOM, not canvas text, so the readability gate can measure it.\n\nimport {\n css,\n fitValueFont,\n fitValueFontCss,\n FIT_MIN_FONT_PX,\n fontVar,\n spaceVar,\n useStickyChars,\n} from '@devicechain/widget-sdk';\n\nimport type { LevelStrings } from './level-strings';\nimport type { ScalePosition } from './value-scale';\n\n// The room the readout may fill (a box inside the frame's content column) and the density scale.\nexport interface ReadoutRoom {\n size: { width: number; height: number };\n scale: number;\n}\n\n// The words the off-scale marker is labelled with; the only strings the readout itself reads.\nexport type MarkerStrings = Pick<LevelStrings, 'aboveScale' | 'belowScale'>;\n\n// A readout that can be marked off-scale must be handed the marker's label, so an unlabelled\n// marker cannot be written; one that is never marked (the gauge) needs no strings at all, which\n// keeps the level-strings table out of an eager widget's bundle.\nexport type LevelReadoutProps = ReadoutRoom & {\n text: string;\n unit?: string;\n color?: string;\n} & (\n | {\n // Set when the value lies off the scale: a marker is drawn beside it.\n position?: ScalePosition;\n strings: MarkerStrings;\n }\n | { position?: undefined; strings?: undefined }\n );\n\nconst MARKER = { above: '\u25B2', below: '\u25BC' } as const;\n\nexport function LevelReadout({ text, unit, color, position, size, scale, strings }: LevelReadoutProps) {\n const hasUnit = unit !== undefined && unit !== '';\n const marked = position === 'above' || position === 'below';\n // The unit and the marker render at half size, so each counts for half its characters.\n const chars = useStickyChars(text.length + (hasUnit ? Math.ceil(unit.length / 2) + 1 : 0) + (marked ? 1 : 0));\n const floor = Math.round(FIT_MIN_FONT_PX * scale);\n const measured = size.width > 0 && size.height > 0;\n const px = measured ? fitValueFont(size, chars, { floor }) : undefined;\n const font = px ?? fitValueFontCss(chars, { floor });\n const half = px !== undefined ? Math.round(px / 2) : `calc(${font} / 2)`;\n return (\n <div\n data-level-readout=\"\"\n style={{\n fontSize: font,\n fontWeight: 600,\n lineHeight: 1.1,\n whiteSpace: 'nowrap',\n fontVariantNumeric: 'tabular-nums',\n color: color ?? css('foreground'),\n textAlign: 'center',\n }}\n >\n {text}\n {hasUnit ? (\n <span style={{ fontSize: half, color: css('muted-foreground'), marginLeft: spaceVar(1) }}>{unit}</span>\n ) : null}\n {marked ? (\n <span\n role=\"img\"\n aria-label={position === 'above' ? strings.aboveScale : strings.belowScale}\n title={position === 'above' ? strings.aboveScale : strings.belowScale}\n style={{ fontSize: half, color: css('muted-foreground'), marginLeft: spaceVar(1) }}\n >\n {MARKER[position]}\n </span>\n ) : null}\n </div>\n );\n}\n\n// Why there is nothing to draw: a short sentence in the widget's own box, never a fake value.\nexport function LevelEmpty({ message }: { message: string }) {\n return (\n <div\n data-level-empty=\"\"\n style={{\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n height: '100%',\n textAlign: 'center',\n color: css('muted-foreground'),\n fontSize: fontVar('sm'),\n overflowWrap: 'anywhere',\n }}\n >\n {message}\n </div>\n );\n}\n\n"],
|
|
5
5
|
"mappings": ";AAMA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AA2CH,SAcI,KAdJ;AAbJ,IAAM,SAAS,EAAE,OAAO,UAAK,OAAO,SAAI;AAEjC,SAAS,aAAa,EAAE,MAAM,MAAM,OAAO,UAAU,MAAM,OAAO,QAAQ,GAAsB;AACrG,QAAM,UAAU,SAAS,UAAa,SAAS;AAC/C,QAAM,SAAS,aAAa,WAAW,aAAa;AAEpD,QAAM,QAAQ,eAAe,KAAK,UAAU,UAAU,KAAK,KAAK,KAAK,SAAS,CAAC,IAAI,IAAI,MAAM,SAAS,IAAI,EAAE;AAC5G,QAAM,QAAQ,KAAK,MAAM,kBAAkB,KAAK;AAChD,QAAM,WAAW,KAAK,QAAQ,KAAK,KAAK,SAAS;AACjD,QAAM,KAAK,WAAW,aAAa,MAAM,OAAO,EAAE,MAAM,CAAC,IAAI;AAC7D,QAAM,OAAO,MAAM,gBAAgB,OAAO,EAAE,MAAM,CAAC;AACnD,QAAM,OAAO,OAAO,SAAY,KAAK,MAAM,KAAK,CAAC,IAAI,QAAQ,IAAI;AACjE,SACE;AAAA,IAAC;AAAA;AAAA,MACC,sBAAmB;AAAA,MACnB,OAAO;AAAA,QACL,UAAU;AAAA,QACV,YAAY;AAAA,QACZ,YAAY;AAAA,QACZ,YAAY;AAAA,QACZ,oBAAoB;AAAA,QACpB,OAAO,SAAS,IAAI,YAAY;AAAA,QAChC,WAAW;AAAA,MACb;AAAA,MAEC;AAAA;AAAA,QACA,UACC,oBAAC,UAAK,OAAO,EAAE,UAAU,MAAM,OAAO,IAAI,kBAAkB,GAAG,YAAY,SAAS,CAAC,EAAE,GAAI,gBAAK,IAC9F;AAAA,QACH,SACC;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,cAAY,aAAa,UAAU,QAAQ,aAAa,QAAQ;AAAA,YAChE,OAAO,aAAa,UAAU,QAAQ,aAAa,QAAQ;AAAA,YAC3D,OAAO,EAAE,UAAU,MAAM,OAAO,IAAI,kBAAkB,GAAG,YAAY,SAAS,CAAC,EAAE;AAAA,YAEhF,iBAAO,QAAQ;AAAA;AAAA,QAClB,IACE;AAAA;AAAA;AAAA,EACN;AAEJ;AAGO,SAAS,WAAW,EAAE,QAAQ,GAAwB;AAC3D,SACE;AAAA,IAAC;AAAA;AAAA,MACC,oBAAiB;AAAA,MACjB,OAAO;AAAA,QACL,SAAS;AAAA,QACT,YAAY;AAAA,QACZ,gBAAgB;AAAA,QAChB,QAAQ;AAAA,QACR,WAAW;AAAA,QACX,OAAO,IAAI,kBAAkB;AAAA,QAC7B,UAAU,QAAQ,IAAI;AAAA,QACtB,cAAc;AAAA,MAChB;AAAA,MAEC;AAAA;AAAA,EACH;AAEJ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// src/no-reading-core.tsx
|
|
2
|
+
import { formatAge, formatValue, useWidgetDeclaredMetrics } from "@devicechain/widget-sdk";
|
|
3
|
+
import { lastValueHint, WidgetEmptyState } from "@devicechain/widget-sdk/empty-state";
|
|
4
|
+
import { useEffect, useState } from "react";
|
|
5
|
+
import { jsx } from "react/jsx-runtime";
|
|
6
|
+
var ASK_DELAY_MS = 300;
|
|
7
|
+
var SETTLE_MS = 1500;
|
|
8
|
+
function emptyKindFor(input) {
|
|
9
|
+
if (input.unbound) return "unbound";
|
|
10
|
+
if (input.reported) return "no-data";
|
|
11
|
+
if (input.measurement !== void 0 && input.declared && !input.declared.includes(input.measurement)) return "not-reported";
|
|
12
|
+
return "no-data";
|
|
13
|
+
}
|
|
14
|
+
function lastReadingHint(sample, options = {}) {
|
|
15
|
+
if (!sample || sample.value == null) return void 0;
|
|
16
|
+
const value = formatValue(sample.value, options.precision);
|
|
17
|
+
const at = sample.occurredTime ? Date.parse(sample.occurredTime) : NaN;
|
|
18
|
+
if (Number.isNaN(at)) return void 0;
|
|
19
|
+
const age = Math.max(0, Math.floor(((options.now ?? Date.now()) - at) / 1e3));
|
|
20
|
+
return lastValueHint(options.unit ? `${value} ${options.unit}` : value, formatAge(age));
|
|
21
|
+
}
|
|
22
|
+
function NoReading({ unbound, measurement, lastSample, unit, precision, detail, waitMs = SETTLE_MS }) {
|
|
23
|
+
const { declared, ask } = useWidgetDeclaredMetrics();
|
|
24
|
+
const [settled, setSettled] = useState(waitMs <= 0);
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
if (settled || unbound) return;
|
|
27
|
+
const timer = setTimeout(() => setSettled(true), waitMs);
|
|
28
|
+
return () => clearTimeout(timer);
|
|
29
|
+
}, [settled, unbound, waitMs]);
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
if (unbound || measurement === void 0) return;
|
|
32
|
+
const timer = setTimeout(ask, ASK_DELAY_MS);
|
|
33
|
+
return () => clearTimeout(timer);
|
|
34
|
+
}, [ask, unbound, measurement]);
|
|
35
|
+
if (!unbound && !settled) return /* @__PURE__ */ jsx("div", { "data-empty-pending": "", "aria-hidden": "true", style: { width: "100%", height: "100%" } });
|
|
36
|
+
const kind = emptyKindFor({ unbound, measurement, declared, reported: lastSample !== void 0 });
|
|
37
|
+
const hint = kind === "no-data" ? lastReadingHint(lastSample, { unit, precision }) : void 0;
|
|
38
|
+
return /* @__PURE__ */ jsx(WidgetEmptyState, { kind, measurement, hint, detail: kind === "no-data" ? detail : void 0 });
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export {
|
|
42
|
+
ASK_DELAY_MS,
|
|
43
|
+
SETTLE_MS,
|
|
44
|
+
emptyKindFor,
|
|
45
|
+
lastReadingHint,
|
|
46
|
+
NoReading
|
|
47
|
+
};
|
|
48
|
+
//# sourceMappingURL=chunk-JVC6JYUR.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/no-reading-core.tsx"],
|
|
4
|
+
"sourcesContent": ["// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// What a single-device widget shows when it has no reading to draw, and WHY. One decision shared by\n// every built-in, so a board does not explain the same absence three ways:\n//\n// the slot has no device bound -> unbound\n// the device's active profile does not declare it -> not-reported (it will never report this)\n// otherwise -> no-data (declared, but nothing in the window)\n//\n// \"Declared\" is asked of the host only when this renders (useWidgetDeclaredMetrics.ask), so a board\n// whose widgets have data pays nothing. While the answer is pending, or when the host cannot give\n// one, the declaration is unknown, and unknown is read as declared: the widget says \"no data\" rather\n// than confidently claiming the device never reports it. A reading of the measurement that the\n// widget does hold (outside its window) proves the device reports it, whatever the profile says.\n//\n// It is apart from no-reading.tsx (the level and fleet wrappers) so a widget needing only this,\n// the table, does not pull the wrappers' imports into its chunk: that one edge moved part of the\n// first download into a chunk of its own in the replay build, about 600 B gzip.\n\nimport type { MeasurementSample } from '@devicechain/dashboards';\nimport { formatAge, formatValue, useWidgetDeclaredMetrics } from '@devicechain/widget-sdk';\nimport { lastValueHint, WidgetEmptyState, type WidgetEmptyKind } from '@devicechain/widget-sdk/empty-state';\nimport { useEffect, useState } from 'react';\n\n// How long a widget stays empty before it asks the host what the device's profile declares.\nexport const ASK_DELAY_MS = 300;\n\n// How long a widget with no reading stays blank before it says why. A board that is loading has no\n// reading yet, and \"nothing was reported in this range\" would be false for the moment it takes the\n// first one to arrive. A caller that has already waited (or knows the read has settled) passes 0.\nexport const SETTLE_MS = 1500;\n\nexport function emptyKindFor(input: {\n unbound: boolean;\n measurement: string | undefined;\n declared: readonly string[] | null | undefined;\n // The widget holds a reading of the measurement (outside what it shows): the device reports it.\n reported?: boolean;\n}): Extract<WidgetEmptyKind, 'unbound' | 'not-reported' | 'no-data'> {\n if (input.unbound) return 'unbound';\n if (input.reported) return 'no-data';\n if (input.measurement !== undefined && input.declared && !input.declared.includes(input.measurement)) return 'not-reported';\n return 'no-data';\n}\n\n// \"Last value 21.4 \u00B0C \u00B7 3 days ago\", for a reading that exists but lies outside what the widget shows.\nexport function lastReadingHint(\n sample: MeasurementSample | undefined,\n options: { unit?: string; precision?: number; now?: number } = {},\n): string | undefined {\n if (!sample || sample.value == null) return undefined;\n const value = formatValue(sample.value, options.precision);\n const at = sample.occurredTime ? Date.parse(sample.occurredTime) : NaN;\n if (Number.isNaN(at)) return undefined;\n const age = Math.max(0, Math.floor(((options.now ?? Date.now()) - at) / 1000));\n return lastValueHint(options.unit ? `${value} ${options.unit}` : value, formatAge(age));\n}\n\nexport interface NoReadingProps {\n // The slot has no device bound.\n unbound: boolean;\n // The measurement the widget was asked for; undefined when it shows whatever arrives.\n measurement: string | undefined;\n // A reading known to exist but outside the window, shown as the hint of a no-data state.\n lastSample?: MeasurementSample;\n unit?: string;\n precision?: number;\n // Replaces the stock no-data explanation (a stat card's \"No readings in the last 1h\").\n detail?: string;\n // How long to stay blank before explaining (SETTLE_MS when omitted). An unbound slot never waits.\n waitMs?: number;\n}\n\nexport function NoReading({ unbound, measurement, lastSample, unit, precision, detail, waitMs = SETTLE_MS }: NoReadingProps) {\n const { declared, ask } = useWidgetDeclaredMetrics();\n const [settled, setSettled] = useState(waitMs <= 0);\n useEffect(() => {\n if (settled || unbound) return;\n const timer = setTimeout(() => setSettled(true), waitMs);\n return () => clearTimeout(timer);\n }, [settled, unbound, waitMs]);\n // After a moment, not at once: on a board that is loading, the reading is about to arrive and the\n // lookup would be for nothing.\n useEffect(() => {\n if (unbound || measurement === undefined) return;\n const timer = setTimeout(ask, ASK_DELAY_MS);\n return () => clearTimeout(timer);\n }, [ask, unbound, measurement]);\n if (!unbound && !settled) return <div data-empty-pending=\"\" aria-hidden=\"true\" style={{ width: '100%', height: '100%' }} />;\n const kind = emptyKindFor({ unbound, measurement, declared, reported: lastSample !== undefined });\n const hint = kind === 'no-data' ? lastReadingHint(lastSample, { unit, precision }) : undefined;\n return <WidgetEmptyState kind={kind} measurement={measurement} hint={hint} detail={kind === 'no-data' ? detail : undefined} />;\n}\n\n"],
|
|
5
|
+
"mappings": ";AAqBA,SAAS,WAAW,aAAa,gCAAgC;AACjE,SAAS,eAAe,wBAA8C;AACtE,SAAS,WAAW,gBAAgB;AAkED;AA/D5B,IAAM,eAAe;AAKrB,IAAM,YAAY;AAElB,SAAS,aAAa,OAMwC;AACnE,MAAI,MAAM,QAAS,QAAO;AAC1B,MAAI,MAAM,SAAU,QAAO;AAC3B,MAAI,MAAM,gBAAgB,UAAa,MAAM,YAAY,CAAC,MAAM,SAAS,SAAS,MAAM,WAAW,EAAG,QAAO;AAC7G,SAAO;AACT;AAGO,SAAS,gBACd,QACA,UAA+D,CAAC,GAC5C;AACpB,MAAI,CAAC,UAAU,OAAO,SAAS,KAAM,QAAO;AAC5C,QAAM,QAAQ,YAAY,OAAO,OAAO,QAAQ,SAAS;AACzD,QAAM,KAAK,OAAO,eAAe,KAAK,MAAM,OAAO,YAAY,IAAI;AACnE,MAAI,OAAO,MAAM,EAAE,EAAG,QAAO;AAC7B,QAAM,MAAM,KAAK,IAAI,GAAG,KAAK,QAAQ,QAAQ,OAAO,KAAK,IAAI,KAAK,MAAM,GAAI,CAAC;AAC7E,SAAO,cAAc,QAAQ,OAAO,GAAG,KAAK,IAAI,QAAQ,IAAI,KAAK,OAAO,UAAU,GAAG,CAAC;AACxF;AAiBO,SAAS,UAAU,EAAE,SAAS,aAAa,YAAY,MAAM,WAAW,QAAQ,SAAS,UAAU,GAAmB;AAC3H,QAAM,EAAE,UAAU,IAAI,IAAI,yBAAyB;AACnD,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,UAAU,CAAC;AAClD,YAAU,MAAM;AACd,QAAI,WAAW,QAAS;AACxB,UAAM,QAAQ,WAAW,MAAM,WAAW,IAAI,GAAG,MAAM;AACvD,WAAO,MAAM,aAAa,KAAK;AAAA,EACjC,GAAG,CAAC,SAAS,SAAS,MAAM,CAAC;AAG7B,YAAU,MAAM;AACd,QAAI,WAAW,gBAAgB,OAAW;AAC1C,UAAM,QAAQ,WAAW,KAAK,YAAY;AAC1C,WAAO,MAAM,aAAa,KAAK;AAAA,EACjC,GAAG,CAAC,KAAK,SAAS,WAAW,CAAC;AAC9B,MAAI,CAAC,WAAW,CAAC,QAAS,QAAO,oBAAC,SAAI,sBAAmB,IAAG,eAAY,QAAO,OAAO,EAAE,OAAO,QAAQ,QAAQ,OAAO,GAAG;AACzH,QAAM,OAAO,aAAa,EAAE,SAAS,aAAa,UAAU,UAAU,eAAe,OAAU,CAAC;AAChG,QAAM,OAAO,SAAS,YAAY,gBAAgB,YAAY,EAAE,MAAM,UAAU,CAAC,IAAI;AACrF,SAAO,oBAAC,oBAAiB,MAAY,aAA0B,MAAY,QAAQ,SAAS,YAAY,SAAS,QAAW;AAC9H;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -40,6 +40,9 @@ function measurementIsMembersOnly(descriptor) {
|
|
|
40
40
|
function presenceMode(descriptor) {
|
|
41
41
|
return policy(descriptor).presence?.const === true;
|
|
42
42
|
}
|
|
43
|
+
function historyMode(descriptor) {
|
|
44
|
+
return policy(descriptor).history?.const === true;
|
|
45
|
+
}
|
|
43
46
|
function parseAcknowledged(value) {
|
|
44
47
|
if (value === "true") return true;
|
|
45
48
|
if (value === "false") return false;
|
|
@@ -81,9 +84,10 @@ export {
|
|
|
81
84
|
membersMode,
|
|
82
85
|
measurementIsMembersOnly,
|
|
83
86
|
presenceMode,
|
|
87
|
+
historyMode,
|
|
84
88
|
alarmSubscription,
|
|
85
89
|
groupsAlarmsByDeviceType,
|
|
86
90
|
commandSubscription,
|
|
87
91
|
wantsEventMarkers
|
|
88
92
|
};
|
|
89
|
-
//# sourceMappingURL=chunk-
|
|
93
|
+
//# sourceMappingURL=chunk-NYHNQ5VR.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/binding-policy.ts"],
|
|
4
|
-
"sourcesContent": ["// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// The channel adapters' half of a widget's declarative binding policy: turn the descriptor's\n// `binding` (typed constants and references to declared option keys) plus the widget's stored\n// options into the subscription arguments a hub channel takes. This is the ONLY place that\n// reads a policy-referenced option, so it reads them by the NAME the descriptor declares \u2014\n// no widget id is consulted, and an extension that declares `{ option: 'rows' }` is honored\n// exactly as a built-in declaring `{ option: 'maxRows' }` is.\n\nimport {\n isValidSpanSeconds,\n type AlarmSubscription,\n type CommandSubscription,\n type WidgetDescriptor,\n type WidgetInstance,\n} from '@devicechain/dashboards';\n\nimport { optBoolean, optNumber, optString } from '@devicechain/widget-sdk';\n\n// The shapes a policy value takes at runtime (the descriptor is validated at registration,\n// so a ref names a declared option of a compatible kind; the value stored in a widget may\n// still be absent or the wrong type, in which case the ref's default applies).\ninterface NumberBinding {\n const?: number;\n option?: string;\n default?: number;\n min?: number;\n}\ninterface StringBinding {\n option?: string;\n default?: string;\n}\n\nfunction numberValue(binding: NumberBinding | undefined, options: WidgetInstance['options']): number | undefined {\n if (!binding) return undefined;\n let value: number | undefined;\n if (typeof binding.const === 'number') value = binding.const;\n else if (binding.option !== undefined) value = optNumber(options, binding.option) ?? binding.default;\n if (value === undefined) return undefined;\n return binding.min !== undefined ? Math.max(binding.min, value) : value;\n}\n\nfunction stringValue(binding: StringBinding | undefined, options: WidgetInstance['options']): string | undefined {\n if (!binding || binding.option === undefined) return undefined;\n return optString(options, binding.option) ?? binding.default;\n}\n\nfunction policy(descriptor: WidgetDescriptor): Record<string, unknown> {\n return descriptor.binding as Record<string, unknown>;\n}\n\n// The rolling-window size a measurement widget asked for, or undefined for the hook default.\nexport function measurementWindow(descriptor: WidgetDescriptor, widget: WidgetInstance): number | undefined {\n return numberValue(policy(descriptor).window as NumberBinding | undefined, widget.options);\n}\n\n// The span (seconds) a TIME-RANGED measurement widget draws, or undefined for one that is not.\n// A widget is time-ranged by declaring `binding.windowSeconds`. Its option pins the span when set\n// to a usable one; otherwise the widget follows the board's range (`boardSpan`, which a viewer\n// can change). An option that is set but not a usable span (under a minute, over a week, not a\n// whole number) is ignored rather than clamped, for the reason the parser gives for the board's\n// own default: clamping turns a typo into a window nobody wrote.\nexport function measurementSpan(\n descriptor: WidgetDescriptor,\n widget: WidgetInstance,\n boardSpan: number | undefined,\n): number | undefined {\n const binding = policy(descriptor).windowSeconds as NumberBinding | undefined;\n if (!binding) return undefined;\n const pinned = numberValue({ ...binding, min: undefined }, widget.options);\n return isValidSpanSeconds(pinned) ? pinned : boardSpan;\n}\n\n// Whether a measurement widget reads its datasource MEMBER BY MEMBER (a fleet widget: a row or a\n// bar per device of an anchor) rather than as one stream keyed by measurement name. The descriptor\n// says so with `binding.members`: always (`const`), or when a declared option holds a given value\n// (a bar chart set to one bar per device).\nexport function membersMode(descriptor: WidgetDescriptor, widget: WidgetInstance): boolean {\n const members = policy(descriptor).members as { const?: boolean; option?: string; equals?: string } | undefined;\n if (!members) return false;\n if (members.const === true) return true;\n return members.option !== undefined && optString(widget.options, members.option) === members.equals;\n}\n\n// Whether a widget's `measurement` option is the one its MEMBERS mode compares, and so means nothing\n// outside that mode: the descriptor gates members mode on an option (a chart set to one line or one\n// bar per device) rather than always reading member by member. Read one device at a time, such a\n// widget draws every measurement its datasource selects, so a `measurement` left stored from the\n// members mode must not narrow anything (its freshness is the newest of what it draws).\nexport function measurementIsMembersOnly(descriptor: WidgetDescriptor): boolean {\n const members = policy(descriptor).members as { const?: boolean; option?: string } | undefined;\n return members !== undefined && members.const !== true && members.option !== undefined;\n}\n\n// Whether a measurement widget reads its devices' CONNECTIVITY (online or offline, last seen) rather\n// than their readings. The descriptor says so with `binding.presence`. The feed that reads it is its\n// own chunk (presence-feed.tsx), loaded only for such a widget.\nexport function presenceMode(descriptor: WidgetDescriptor): boolean {\n return (policy(descriptor).presence as { const?: boolean } | undefined)?.const === true;\n}\n\n// The acknowledged filter is stored as 'true' / 'false' (or absent = any).\nfunction parseAcknowledged(value: string | undefined): boolean | undefined {\n if (value === 'true') return true;\n if (value === 'false') return false;\n return undefined;\n}\n\n// An alarm widget's scope + filters. The datasource (undefined = tenant-wide) is the\n// renderer's; the page size and filters come from the policy.\nexport function alarmSubscription(descriptor: WidgetDescriptor, widget: WidgetInstance): AlarmSubscription {\n const p = policy(descriptor);\n const deviceType = stringValue(p.deviceType as StringBinding | undefined, widget.options);\n return {\n datasource: widget.datasource,\n state: stringValue(p.state as StringBinding | undefined, widget.options),\n severity: stringValue(p.severity as StringBinding | undefined, widget.options),\n acknowledged: parseAcknowledged(stringValue(p.acknowledged as StringBinding | undefined, widget.options)),\n pageSize: numberValue(p.pageSize as NumberBinding | undefined, widget.options) ?? 1,\n // A widget that declares `binding.countBySeverity` reads counts, not rows; `binding.deviceType`\n // narrows them to one device type (a blank option is no type).\n ...((p.countBySeverity as { const?: boolean } | undefined)?.const === true ? { countBySeverity: true } : {}),\n ...(deviceType ? { deviceType } : {}),\n };\n}\n\n// Whether an alarm widget groups its alarms by the originating device's type (a tab strip). The\n// descriptor says so with `binding.groupBy`: a declared option holding a given value turns it on.\nexport function groupsAlarmsByDeviceType(descriptor: WidgetDescriptor, widget: WidgetInstance): boolean {\n const groupBy = policy(descriptor).groupBy as { option?: string; equals?: string } | undefined;\n return groupBy?.option !== undefined && optString(widget.options, groupBy.option) === groupBy.equals;\n}\n\n// A command widget's target scope and how many recent commands its history shows.\nexport function commandSubscription(descriptor: WidgetDescriptor, widget: WidgetInstance): CommandSubscription {\n return {\n datasource: widget.datasource,\n pageSize: numberValue(policy(descriptor).pageSize as NumberBinding | undefined, widget.options) ?? 1,\n };\n}\n\n// Whether a measurement widget draws alarm or command markers: its descriptor declares the marker\n// options and one of them is on. The feed that reads them is its own chunk (event-markers-feed.tsx),\n// loaded only when this is true.\nexport function wantsEventMarkers(descriptor: WidgetDescriptor, widget: WidgetInstance): boolean {\n const declared = descriptor.options as Record<string, unknown>;\n return (\n (declared.markAlarms !== undefined && optBoolean(widget.options, 'markAlarms') === true) ||\n (declared.markCommands !== undefined && optBoolean(widget.options, 'markCommands') === true)\n );\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// The channel adapters' half of a widget's declarative binding policy: turn the descriptor's\n// `binding` (typed constants and references to declared option keys) plus the widget's stored\n// options into the subscription arguments a hub channel takes. This is the ONLY place that\n// reads a policy-referenced option, so it reads them by the NAME the descriptor declares \u2014\n// no widget id is consulted, and an extension that declares `{ option: 'rows' }` is honored\n// exactly as a built-in declaring `{ option: 'maxRows' }` is.\n\nimport {\n isValidSpanSeconds,\n type AlarmSubscription,\n type CommandSubscription,\n type WidgetDescriptor,\n type WidgetInstance,\n} from '@devicechain/dashboards';\n\nimport { optBoolean, optNumber, optString } from '@devicechain/widget-sdk';\n\n// The shapes a policy value takes at runtime (the descriptor is validated at registration,\n// so a ref names a declared option of a compatible kind; the value stored in a widget may\n// still be absent or the wrong type, in which case the ref's default applies).\ninterface NumberBinding {\n const?: number;\n option?: string;\n default?: number;\n min?: number;\n}\ninterface StringBinding {\n option?: string;\n default?: string;\n}\n\nfunction numberValue(binding: NumberBinding | undefined, options: WidgetInstance['options']): number | undefined {\n if (!binding) return undefined;\n let value: number | undefined;\n if (typeof binding.const === 'number') value = binding.const;\n else if (binding.option !== undefined) value = optNumber(options, binding.option) ?? binding.default;\n if (value === undefined) return undefined;\n return binding.min !== undefined ? Math.max(binding.min, value) : value;\n}\n\nfunction stringValue(binding: StringBinding | undefined, options: WidgetInstance['options']): string | undefined {\n if (!binding || binding.option === undefined) return undefined;\n return optString(options, binding.option) ?? binding.default;\n}\n\nfunction policy(descriptor: WidgetDescriptor): Record<string, unknown> {\n return descriptor.binding as Record<string, unknown>;\n}\n\n// The rolling-window size a measurement widget asked for, or undefined for the hook default.\nexport function measurementWindow(descriptor: WidgetDescriptor, widget: WidgetInstance): number | undefined {\n return numberValue(policy(descriptor).window as NumberBinding | undefined, widget.options);\n}\n\n// The span (seconds) a TIME-RANGED measurement widget draws, or undefined for one that is not.\n// A widget is time-ranged by declaring `binding.windowSeconds`. Its option pins the span when set\n// to a usable one; otherwise the widget follows the board's range (`boardSpan`, which a viewer\n// can change). An option that is set but not a usable span (under a minute, over a week, not a\n// whole number) is ignored rather than clamped, for the reason the parser gives for the board's\n// own default: clamping turns a typo into a window nobody wrote.\nexport function measurementSpan(\n descriptor: WidgetDescriptor,\n widget: WidgetInstance,\n boardSpan: number | undefined,\n): number | undefined {\n const binding = policy(descriptor).windowSeconds as NumberBinding | undefined;\n if (!binding) return undefined;\n const pinned = numberValue({ ...binding, min: undefined }, widget.options);\n return isValidSpanSeconds(pinned) ? pinned : boardSpan;\n}\n\n// Whether a measurement widget reads its datasource MEMBER BY MEMBER (a fleet widget: a row or a\n// bar per device of an anchor) rather than as one stream keyed by measurement name. The descriptor\n// says so with `binding.members`: always (`const`), or when a declared option holds a given value\n// (a bar chart set to one bar per device).\nexport function membersMode(descriptor: WidgetDescriptor, widget: WidgetInstance): boolean {\n const members = policy(descriptor).members as { const?: boolean; option?: string; equals?: string } | undefined;\n if (!members) return false;\n if (members.const === true) return true;\n return members.option !== undefined && optString(widget.options, members.option) === members.equals;\n}\n\n// Whether a widget's `measurement` option is the one its MEMBERS mode compares, and so means nothing\n// outside that mode: the descriptor gates members mode on an option (a chart set to one line or one\n// bar per device) rather than always reading member by member. Read one device at a time, such a\n// widget draws every measurement its datasource selects, so a `measurement` left stored from the\n// members mode must not narrow anything (its freshness is the newest of what it draws).\nexport function measurementIsMembersOnly(descriptor: WidgetDescriptor): boolean {\n const members = policy(descriptor).members as { const?: boolean; option?: string } | undefined;\n return members !== undefined && members.const !== true && members.option !== undefined;\n}\n\n// Whether a measurement widget reads its devices' CONNECTIVITY (online or offline, last seen) rather\n// than their readings. The descriptor says so with `binding.presence`. The feed that reads it is its\n// own chunk (presence-feed.tsx), loaded only for such a widget.\nexport function presenceMode(descriptor: WidgetDescriptor): boolean {\n return (policy(descriptor).presence as { const?: boolean } | undefined)?.const === true;\n}\n\n// Whether a measurement widget reads one device's STORED HISTORY in time buckets rather than the live\n// feed (`binding.history`). The feed that reads it is its own chunk (history-feed.tsx), loaded only for\n// such a widget.\nexport function historyMode(descriptor: WidgetDescriptor): boolean {\n return (policy(descriptor).history as { const?: boolean } | undefined)?.const === true;\n}\n\n// The acknowledged filter is stored as 'true' / 'false' (or absent = any).\nfunction parseAcknowledged(value: string | undefined): boolean | undefined {\n if (value === 'true') return true;\n if (value === 'false') return false;\n return undefined;\n}\n\n// An alarm widget's scope + filters. The datasource (undefined = tenant-wide) is the\n// renderer's; the page size and filters come from the policy.\nexport function alarmSubscription(descriptor: WidgetDescriptor, widget: WidgetInstance): AlarmSubscription {\n const p = policy(descriptor);\n const deviceType = stringValue(p.deviceType as StringBinding | undefined, widget.options);\n return {\n datasource: widget.datasource,\n state: stringValue(p.state as StringBinding | undefined, widget.options),\n severity: stringValue(p.severity as StringBinding | undefined, widget.options),\n acknowledged: parseAcknowledged(stringValue(p.acknowledged as StringBinding | undefined, widget.options)),\n pageSize: numberValue(p.pageSize as NumberBinding | undefined, widget.options) ?? 1,\n // A widget that declares `binding.countBySeverity` reads counts, not rows; `binding.deviceType`\n // narrows them to one device type (a blank option is no type).\n ...((p.countBySeverity as { const?: boolean } | undefined)?.const === true ? { countBySeverity: true } : {}),\n ...(deviceType ? { deviceType } : {}),\n };\n}\n\n// Whether an alarm widget groups its alarms by the originating device's type (a tab strip). The\n// descriptor says so with `binding.groupBy`: a declared option holding a given value turns it on.\nexport function groupsAlarmsByDeviceType(descriptor: WidgetDescriptor, widget: WidgetInstance): boolean {\n const groupBy = policy(descriptor).groupBy as { option?: string; equals?: string } | undefined;\n return groupBy?.option !== undefined && optString(widget.options, groupBy.option) === groupBy.equals;\n}\n\n// A command widget's target scope and how many recent commands its history shows.\nexport function commandSubscription(descriptor: WidgetDescriptor, widget: WidgetInstance): CommandSubscription {\n return {\n datasource: widget.datasource,\n pageSize: numberValue(policy(descriptor).pageSize as NumberBinding | undefined, widget.options) ?? 1,\n };\n}\n\n// Whether a measurement widget draws alarm or command markers: its descriptor declares the marker\n// options and one of them is on. The feed that reads them is its own chunk (event-markers-feed.tsx),\n// loaded only when this is true.\nexport function wantsEventMarkers(descriptor: WidgetDescriptor, widget: WidgetInstance): boolean {\n const declared = descriptor.options as Record<string, unknown>;\n return (\n (declared.markAlarms !== undefined && optBoolean(widget.options, 'markAlarms') === true) ||\n (declared.markCommands !== undefined && optBoolean(widget.options, 'markCommands') === true)\n );\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|