@devicechain/widgets 0.23.0 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/README.md +8 -3
  2. package/dist/aggregate-fold.d.ts +3 -2
  3. package/dist/aggregate-strings.d.ts +1 -0
  4. package/dist/alarm-strings.d.ts +18 -0
  5. package/dist/binding-policy.d.ts +4 -0
  6. package/dist/bound-devices-notice.d.ts +30 -0
  7. package/dist/builtin-descriptors.d.ts +290 -249
  8. package/dist/builtin-option-docs.d.ts +2 -0
  9. package/dist/builtin-registry.d.ts +249 -226
  10. package/dist/category-geometry.d.ts +1 -0
  11. package/dist/chunks/{aggregate-feed-BD3CHNFU.js → aggregate-feed-4FSBQYJL.js} +39 -11
  12. package/dist/chunks/aggregate-feed-4FSBQYJL.js.map +7 -0
  13. package/dist/chunks/{aggregate-widgets-HZQI5HTX.js → aggregate-widgets-2ZQBW3YF.js} +376 -182
  14. package/dist/chunks/aggregate-widgets-2ZQBW3YF.js.map +7 -0
  15. package/dist/chunks/alarm-widgets-X4PX5MYJ.js +72 -0
  16. package/dist/chunks/alarm-widgets-X4PX5MYJ.js.map +7 -0
  17. package/dist/chunks/chunk-546TK2QG.js +722 -0
  18. package/dist/chunks/chunk-546TK2QG.js.map +7 -0
  19. package/dist/chunks/{chunk-WBKHYZU2.js → chunk-62CHYCHI.js} +6 -62
  20. package/dist/chunks/chunk-62CHYCHI.js.map +7 -0
  21. package/dist/chunks/chunk-BMFQOTYG.js +41 -0
  22. package/dist/chunks/chunk-BMFQOTYG.js.map +7 -0
  23. package/dist/chunks/{chunk-2D7JNKFE.js → chunk-JFH4QMLA.js} +28 -4
  24. package/dist/chunks/chunk-JFH4QMLA.js.map +7 -0
  25. package/dist/chunks/chunk-QMGKB6J3.js +84 -0
  26. package/dist/chunks/chunk-QMGKB6J3.js.map +7 -0
  27. package/dist/chunks/chunk-XNEBAY4J.js +19 -0
  28. package/dist/chunks/chunk-XNEBAY4J.js.map +7 -0
  29. package/dist/chunks/chunk-YYJV54BV.js +58 -0
  30. package/dist/chunks/chunk-YYJV54BV.js.map +7 -0
  31. package/dist/chunks/compare-feed-AY3CVKUZ.js +346 -0
  32. package/dist/chunks/compare-feed-AY3CVKUZ.js.map +7 -0
  33. package/dist/chunks/{control-widgets-3V2LSSRF.js → control-widgets-UCTJJJ2D.js} +267 -78
  34. package/dist/chunks/control-widgets-UCTJJJ2D.js.map +7 -0
  35. package/dist/chunks/event-markers-feed-GEGTIHXT.js +373 -0
  36. package/dist/chunks/event-markers-feed-GEGTIHXT.js.map +7 -0
  37. package/dist/chunks/{fleet-feed-5QGDLOV5.js → fleet-feed-2SKDR6ZQ.js} +47 -8
  38. package/dist/chunks/fleet-feed-2SKDR6ZQ.js.map +7 -0
  39. package/dist/chunks/presence-widgets-2LIUKN4Y.js +350 -0
  40. package/dist/chunks/presence-widgets-2LIUKN4Y.js.map +7 -0
  41. package/dist/command-control-strings.d.ts +5 -0
  42. package/dist/compare-feed.d.ts +13 -0
  43. package/dist/compare-model.d.ts +38 -0
  44. package/dist/compare-strings.d.ts +13 -0
  45. package/dist/documented-descriptors.d.ts +4826 -0
  46. package/dist/event-markers-feed.d.ts +11 -0
  47. package/dist/event-markers-model.d.ts +38 -0
  48. package/dist/event-markers-strings.d.ts +20 -0
  49. package/dist/event-markers-types.d.ts +7 -0
  50. package/dist/fleet-count-model.d.ts +13 -0
  51. package/dist/fleet-feed.d.ts +1 -0
  52. package/dist/fleet-model.d.ts +1 -0
  53. package/dist/fleet-strings.d.ts +11 -0
  54. package/dist/index.d.ts +6 -3
  55. package/dist/index.js +946 -398
  56. package/dist/index.js.map +4 -4
  57. package/dist/presence-feed.d.ts +24 -0
  58. package/dist/presence-model.d.ts +13 -0
  59. package/dist/presence-strings.d.ts +19 -0
  60. package/dist/sparkline.d.ts +23 -0
  61. package/dist/use-bound-devices.d.ts +26 -0
  62. package/dist/widgets/aggregate-lazy.d.ts +81 -46
  63. package/dist/widgets/aggregate-widgets.d.ts +1 -0
  64. package/dist/widgets/alarm-grouped.d.ts +17 -0
  65. package/dist/widgets/alarm-lazy.d.ts +63 -16
  66. package/dist/widgets/alarm-severity.d.ts +8 -0
  67. package/dist/widgets/alarm-table.d.ts +2 -0
  68. package/dist/widgets/alarm-type-tabs.d.ts +10 -0
  69. package/dist/widgets/alarm-widgets.d.ts +2 -0
  70. package/dist/widgets/command-stepper.d.ts +22 -0
  71. package/dist/widgets/control-lazy.d.ts +16 -24
  72. package/dist/widgets/device-status.d.ts +4 -0
  73. package/dist/widgets/fleet-count.d.ts +2 -0
  74. package/dist/widgets/gauge.d.ts +2 -15
  75. package/dist/widgets/image.d.ts +2 -6
  76. package/dist/widgets/label.d.ts +2 -6
  77. package/dist/widgets/latest-card.d.ts +2 -13
  78. package/dist/widgets/lazy-chunk.d.ts +7 -2
  79. package/dist/widgets/level-lazy.d.ts +14 -96
  80. package/dist/widgets/map.d.ts +8 -5
  81. package/dist/widgets/online-doughnut.d.ts +2 -0
  82. package/dist/widgets/presence-lazy.d.ts +27 -0
  83. package/dist/widgets/presence-widgets.d.ts +3 -0
  84. package/dist/widgets/selection-lazy.d.ts +2 -4
  85. package/dist/widgets/table-lazy.d.ts +2 -7
  86. package/dist/widgets/time-series-chart.d.ts +62 -12
  87. package/package.json +13 -13
  88. package/dist/chunks/aggregate-feed-BD3CHNFU.js.map +0 -7
  89. package/dist/chunks/aggregate-widgets-HZQI5HTX.js.map +0 -7
  90. package/dist/chunks/alarm-widgets-7UY6MIYZ.js +0 -9
  91. package/dist/chunks/alarm-widgets-7UY6MIYZ.js.map +0 -7
  92. package/dist/chunks/chunk-2D7JNKFE.js.map +0 -7
  93. package/dist/chunks/chunk-6OGI4UFJ.js +0 -367
  94. package/dist/chunks/chunk-6OGI4UFJ.js.map +0 -7
  95. package/dist/chunks/chunk-WBKHYZU2.js.map +0 -7
  96. package/dist/chunks/control-widgets-3V2LSSRF.js.map +0 -7
  97. package/dist/chunks/fleet-feed-5QGDLOV5.js.map +0 -7
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/widgets/severity.ts", "../../src/widgets/alarm-table.tsx", "../../src/alarm-strings.ts", "../../src/widgets/alarm-type-tabs.tsx", "../../src/widgets/alarm-count.tsx", "../../src/widgets/alarm-severity.tsx"],
4
+ "sourcesContent": ["// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// Alarm-severity presentation shared by the alarm widgets (ADR-041 vocabulary:\n// CRITICAL | MAJOR | MINOR | WARNING | INDETERMINATE). Severity is SEMANTIC color --\n// deliberately fixed, not derived from the dashboard accent -- so a critical alarm reads the\n// same red on every tenant's theme.\n//\n// \uD83D\uDD11 ONE PALETTE. The hues are the SDK's severity tokens (`--sev-*`, widget-sdk/severity.ts):\n// the same colour a threshold band uses is the colour an alarm row uses, in light and dark, and\n// a host that re-themes a token re-themes both. This file keeps only the VOCABULARY mapping\n// (the backend's UPPER_CASE severities onto the token levels). INDETERMINATE -- and anything\n// unrecognized -- has no alarm hue: it renders in the muted foreground, the same neutral an\n// absent alarm gets.\n\nimport { css, type AlarmLevel } from '@devicechain/widget-sdk';\n\nconst LEVEL_OF: Record<string, AlarmLevel> = {\n CRITICAL: 'critical',\n MAJOR: 'major',\n MINOR: 'minor',\n WARNING: 'warning',\n};\n\nconst SEVERITY_ORDER: Record<string, number> = {\n CRITICAL: 0,\n MAJOR: 1,\n MINOR: 2,\n WARNING: 3,\n INDETERMINATE: 4,\n};\n\n// severityColor maps a severity to its badge/stripe color. `colors` is useSeverityColors() --\n// the concrete token colours in scope -- so the result follows the host's theme and dark mode.\n// An INDETERMINATE or unrecognized severity (a hand-edited or future one) takes the muted\n// foreground rather than a hue it has not earned.\nexport function severityColor(colors: Readonly<Record<AlarmLevel, string>>, severity: string): string {\n const level = LEVEL_OF[severity];\n return level ? colors[level] : css('muted-foreground');\n}\n\n// severityRank orders severities most-severe-first; unknown severities sort last.\nexport function severityRank(severity: string): number {\n return SEVERITY_ORDER[severity] ?? 99;\n}\n\n// severityLabel renders a severity in Title Case ('CRITICAL' \u2192 'Critical').\nexport function severityLabel(severity: string): string {\n if (!severity) return '';\n return severity.charAt(0).toUpperCase() + severity.slice(1).toLowerCase();\n}\n\n// highestSeverity returns the most severe severity present in a list, or undefined for\n// an empty list \u2014 the accent an alarm-count uses to signal \"how bad is the worst one\".\nexport function highestSeverity(severities: string[]): string | undefined {\n let best: string | undefined;\n for (const s of severities) {\n if (best === undefined || severityRank(s) < severityRank(best)) best = s;\n }\n return best;\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// alarm-table \u2014 the raised alarms in the widget's scope (ADR-041), newest first, with\n// a severity stripe per row. When the runtime supplies an action seam AND the viewer\n// holds alarm:write, each active row gets Acknowledge / Clear controls; otherwise the\n// table is read-only. Bound through the hub's alarm channel, so it renders identically\n// from live data or the synthetic preview source.\n\nimport type { AlarmRow, AlarmTypeTab, WidgetActions } from '@devicechain/dashboards';\nimport { Fragment, useEffect, useId, useState, type CSSProperties, type ReactNode } from 'react';\n\nimport {\n type AlarmStreamState,\n css,\n fontVar,\n FlashValue,\n formatDateTime,\n optBoolean,\n optNumber,\n optString,\n useElementSize,\n useSeverityColors,\n useWidgetSelect,\n useWidgetTimeZone,\n WidgetFrame,\n type WidgetProps,\n type WidgetSelect,\n} from '@devicechain/widget-sdk';\n\nimport { alarmStrings } from '../alarm-strings';\nimport { AlarmTypeTabsStrip, panelDomId, tabDomId } from './alarm-type-tabs';\nimport { severityColor, severityLabel } from './severity';\n\nconst cell: CSSProperties = { padding: '6px 10px', textAlign: 'left', whiteSpace: 'nowrap' };\nconst headCell: CSSProperties = {\n ...cell,\n position: 'sticky',\n top: 0,\n background: css('card'),\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\n// statusLabel collapses the 4-state model (state \u00D7 acknowledged) into the one signal an\n// operator reads, matching the console's alarm badges: cleared wins regardless of the\n// acknowledged flag; an active alarm is \"Acknowledged\" once owned, else \"Active\".\n// Below this measured width the table drops its lowest-value columns (Status, Raised) rather\n// than clipping or scrolling sideways: severity, originator, alarm and value are what an\n// operator reads at a glance, and the text columns wrap instead of pushing the value off the\n// right edge (a phone's single column is ~390 px). Unmeasured (first frame, jsdom) keeps every\n// column on one line.\nexport const ALARM_TABLE_NARROW_PX = 480;\n\n// A narrow table's text cells: tighter padding, and a long originator or alarm key wraps\n// across lines rather than widening the table past its box. Identifiers break at their\n// hyphens first (see hyphenBreaks); `break-word` splits inside a word only when a single\n// segment is wider than the cell. `anywhere` is deliberately NOT used: it also lowers the\n// column's min-content width, so a squeezed column was split mid-word (\"engine-overhea|t\")\n// instead of at a hyphen.\nconst narrowCell: CSSProperties = {\n ...cell,\n padding: '6px 6px',\n whiteSpace: 'normal',\n overflowWrap: 'break-word',\n wordBreak: 'normal',\n};\n\n// hyphenBreaks renders an identifier with a break opportunity after each hyphen, so a\n// hyphenated key (\"tyre-pressure-low\") wraps as \"tyre-/pressure-/low\". The <wbr> also keeps\n// the column's min-content width at the longest segment, which is what lets the table\n// shrink at 390 px without pushing the Value column off the edge.\nexport function hyphenBreaks(text: string): ReactNode {\n // Split after each hyphen, keeping it on the left part. Not a lookbehind regex: this is a\n // published package, and a lookbehind literal is a parse error in older WebKit.\n const parts = text.split('-').map((p, i, all) => (i < all.length - 1 ? `${p}-` : p));\n return parts.map((part, i) => (\n <Fragment key={i}>\n {i > 0 ? <wbr /> : null}\n {part}\n </Fragment>\n ));\n}\n\n// The tabs a type-grouping table shows. By default every device type with an active alarm; an\n// authored `deviceTypes` list (`[{ token }]`) pins WHICH types and their ORDER instead, and a type\n// not listed is under All only. A pinned type with no active alarm still gets its tab (at no badge),\n// named by its token when no alarm has told us its name.\nexport function shownTabs(discovered: readonly AlarmTypeTab[], pinned: readonly string[]): AlarmTypeTab[] {\n if (pinned.length === 0) return [...discovered];\n const byToken = new Map(discovered.map((t) => [t.token, t]));\n const seen = new Set<string>();\n const out: AlarmTypeTab[] = [];\n for (const token of pinned) {\n if (seen.has(token)) continue;\n seen.add(token);\n out.push(byToken.get(token) ?? { token, name: null, active: 0, worstSeverity: null });\n }\n return out;\n}\n\nfunction pinnedTypes(widget: WidgetProps<AlarmStreamState>['widget']): string[] {\n const list = widget.options?.deviceTypes;\n if (!Array.isArray(list)) return [];\n return list.flatMap((item) => {\n const token = (item as { token?: unknown } | null)?.token;\n return typeof token === 'string' && token !== '' ? [token] : [];\n });\n}\n\nfunction statusLabel(alarm: AlarmRow): string {\n if (alarm.state === 'CLEARED') return 'Cleared';\n return alarm.acknowledged ? 'Acknowledged' : 'Active';\n}\n\nexport function AlarmTable({ widget, data, actions }: WidgetProps<AlarmStreamState>) {\n const { alarms, total, loading } = data;\n // Actions render only when the runtime supplies a seam AND the viewer may write.\n const canAct = actions?.can('alarm:write') ?? false;\n const [sizeRef, size] = useElementSize<HTMLDivElement>();\n const severity = useSeverityColors();\n const narrow = size.width > 0 && size.width < ALARM_TABLE_NARROW_PX;\n // Grouped by device type: a tab strip above the table. `typeTabs` is set only for a table whose\n // descriptor groups, so an ungrouped one runs none of this and renders exactly as it always has.\n const typeTabs = data.typeTabs;\n const tabs = typeTabs ? shownTabs(typeTabs.tabs, pinnedTypes(widget)) : [];\n const stripShown = typeTabs !== undefined && tabs.length > 0;\n const selectTab = typeTabs?.select;\n const selectedType = typeTabs?.selected ?? null;\n // A selection the pin dropped must not leave the table filtered to a tab the viewer cannot see.\n const dropped = stripShown && selectedType !== null && !tabs.some((t) => t.token === selectedType);\n useEffect(() => {\n if (dropped) selectTab?.(null);\n }, [dropped, selectTab]);\n const idBase = useId();\n // The type column is redundant inside a type's own tab, and too wide for a phone.\n const typeColumn = stripShown && selectedType === null && !narrow;\n const columns = 5 + (narrow ? 0 : 2) + (typeColumn ? 1 : 0) + (canAct ? 1 : 0);\n const text = narrow ? narrowCell : cell;\n // Round the triggering value to a fixed precision when configured; unset keeps the raw\n // value (formatValue's default), matching the table widget. An alarm's raw lastValue is\n // otherwise a full-width float (e.g. 31.19073834583732).\n const precision = optNumber(widget.options, 'precision');\n // Opt-in directional flash on the triggering value, per row (keyed by alarm token).\n const flash = optBoolean(widget.options, 'flashOnChange');\n // Drill: when a `selectionTarget` slot is configured AND the renderer wired a selection\n // callback (view mode, a host that supports it), a device originator becomes a link that\n // re-points that slot \u2014 driving the per-device widgets to the clicked thermostat\n // (ADR-039 selection amendment). Absent either, the originator is plain text.\n const select = useWidgetSelect();\n const timeZone = useWidgetTimeZone();\n const selectionTarget = optString(widget.options, 'selectionTarget');\n const drillTo = select && selectionTarget ? { select, slot: selectionTarget } : undefined;\n\n return (\n <WidgetFrame title={optString(widget.options, 'title')} flush>\n <div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>\n {stripShown && typeTabs ? (\n <AlarmTypeTabsStrip\n tabs={tabs}\n selected={dropped ? null : selectedType}\n onSelect={typeTabs.select}\n idBase={idBase}\n />\n ) : null}\n <div\n ref={sizeRef}\n style={{ flex: 1, minHeight: 0, overflow: 'auto' }}\n {...(stripShown\n ? { role: 'tabpanel', id: panelDomId(idBase), 'aria-labelledby': tabDomId(idBase, dropped ? null : selectedType) }\n : {})}\n >\n <table\n style={{\n width: '100%',\n borderCollapse: 'collapse',\n fontSize: fontVar('md'),\n color: css('foreground'),\n fontVariantNumeric: 'tabular-nums',\n }}\n >\n <thead>\n <tr>\n <th style={{ ...headCell, width: 4, padding: 0 }} aria-hidden=\"true\" />\n <th scope=\"col\" style={headCell}>\n Severity\n </th>\n {narrow ? null : (\n <th scope=\"col\" style={headCell}>\n Status\n </th>\n )}\n <th scope=\"col\" style={headCell}>\n Originator\n </th>\n {typeColumn ? (\n <th scope=\"col\" style={headCell}>\n {alarmStrings().deviceType}\n </th>\n ) : null}\n <th scope=\"col\" style={headCell}>\n Alarm\n </th>\n <th scope=\"col\" style={{ ...headCell, textAlign: 'right' }}>\n Value\n </th>\n {narrow ? null : (\n <th scope=\"col\" style={headCell}>\n Raised\n </th>\n )}\n {canAct ? (\n <th scope=\"col\" style={{ ...headCell, textAlign: 'right' }}>\n Actions\n </th>\n ) : null}\n </tr>\n </thead>\n <tbody>\n {alarms.length === 0 ? (\n <tr>\n <td colSpan={columns} style={{ ...cell, textAlign: 'center', color: css('muted-foreground') }}>\n {loading ? 'Loading\u2026' : 'No alarms'}\n </td>\n </tr>\n ) : (\n alarms.map((alarm) => {\n const color = severityColor(severity, alarm.severity);\n return (\n <tr key={alarm.token} style={{ borderBottom: `1px solid ${css('border')}` }}>\n <td aria-hidden=\"true\" style={{ padding: 0, width: 4, background: color }} />\n <td style={cell}>\n <span style={{ display: 'inline-flex', alignItems: 'center', gap: 6 }}>\n <span\n aria-hidden=\"true\"\n style={{ width: 8, height: 8, borderRadius: '50%', background: color, flexShrink: 0 }}\n />\n {severityLabel(alarm.severity)}\n </span>\n </td>\n {narrow ? null : <td style={{ ...cell, color: css('muted-foreground') }}>{statusLabel(alarm)}</td>}\n <td style={text}>\n <OriginatorCell alarm={alarm} drillTo={drillTo} />\n </td>\n {typeColumn ? (\n <td style={{ ...cell, color: css('muted-foreground') }}>\n {alarm.deviceType ? (alarm.deviceType.name ?? alarm.deviceType.token) : '\u2014'}\n </td>\n ) : null}\n <td style={text}>\n {hyphenBreaks(alarm.alarmKey)}\n </td>\n <td style={{ ...cell, textAlign: 'right' }}>\n <FlashValue\n value={alarm.lastValue}\n precision={precision}\n enabled={flash}\n identity={alarm.token}\n />\n </td>\n {narrow ? null : (\n <td style={{ ...cell, color: css('muted-foreground') }}>\n {formatDateTime(alarm.raisedTime, timeZone)}\n </td>\n )}\n {canAct ? (\n <td style={{ ...cell, textAlign: 'right' }}>\n {/* actions is defined whenever canAct is true. */}\n <AlarmRowActions alarm={alarm} actions={actions as WidgetActions} />\n </td>\n ) : null}\n </tr>\n );\n })\n )}\n </tbody>\n </table>\n </div>\n {total > alarms.length ? (\n <div\n style={{\n padding: '4px 10px',\n fontSize: fontVar('xs'),\n color: css('muted-foreground'),\n borderTop: `1px solid ${css('border')}`,\n }}\n >\n Showing {alarms.length} of {total}\n </div>\n ) : null}\n </div>\n </WidgetFrame>\n );\n}\n\n// OriginatorCell renders the alarm's originator. When drill is wired AND the originator\n// is a device (it carries a token), it is a link that re-points the target slot to that\n// device; otherwise it is plain text (the token, or the originator type when tokenless).\n// The drill target is a device binding \u2014 a building-scoped alarm table's originators are\n// already members of the context, so the drill stays in-context.\nfunction OriginatorCell({\n alarm,\n drillTo,\n}: {\n alarm: AlarmRow;\n drillTo: { select: WidgetSelect; slot: string } | undefined;\n}) {\n if (drillTo && alarm.originatorType === 'device' && alarm.originatorToken) {\n const token = alarm.originatorToken;\n return (\n <button\n type=\"button\"\n onClick={() => drillTo.select({ slot: drillTo.slot, binding: { kind: 'device', deviceToken: token } })}\n title={`Show ${token} in the linked widgets`}\n style={{\n padding: 0,\n border: 'none',\n background: 'none',\n font: 'inherit',\n color: css('primary'),\n cursor: 'pointer',\n textDecoration: 'underline',\n textUnderlineOffset: 2,\n }}\n >\n {hyphenBreaks(token)}\n </button>\n );\n }\n return <>{hyphenBreaks(alarm.originatorToken ?? alarm.originatorType)}</>;\n}\n\nconst actionButton: CSSProperties = {\n padding: '2px 8px',\n fontSize: fontVar('sm'),\n lineHeight: 1.4,\n borderRadius: 4,\n border: `1px solid ${css('border')}`,\n background: css('card'),\n color: css('foreground'),\n cursor: 'pointer',\n};\n\n// AlarmRowActions owns the pending/error state for one row's Acknowledge / Clear. Only\n// an ACTIVE alarm is actionable (a cleared \u2014 or any future non-active \u2014 state shows\n// \"\u2014\"); an acknowledged one still shows Clear. After a successful mutation the hub\n// reconciles the table, so the row updates itself.\nfunction AlarmRowActions({ alarm, actions }: { alarm: AlarmRow; actions: WidgetActions }) {\n const severity = useSeverityColors();\n const [pending, setPending] = useState<'ack' | 'clear' | null>(null);\n const [failed, setFailed] = useState(false);\n\n if (alarm.state !== 'ACTIVE') {\n return <span style={{ color: css('muted-foreground') }}>\u2014</span>;\n }\n\n const run = (kind: 'ack' | 'clear', op: () => Promise<void>) => {\n setPending(kind);\n setFailed(false);\n // Call op() synchronously (so an immediate dispatch is observable) but guard a\n // synchronous throw too, so a misbehaving seam can't leave the row stuck-disabled.\n let promise: Promise<void>;\n try {\n promise = op();\n } catch {\n setFailed(true);\n setPending(null);\n return;\n }\n promise.catch(() => setFailed(true)).finally(() => setPending(null));\n };\n\n return (\n <span style={{ display: 'inline-flex', gap: 6, justifyContent: 'flex-end', alignItems: 'center' }}>\n {failed ? (\n <span\n role=\"img\"\n aria-label=\"Action failed \u2014 retry\"\n title=\"Action failed \u2014 retry\"\n style={{ color: severityColor(severity, 'CRITICAL'), fontSize: fontVar('sm') }}\n >\n \u26A0\n </span>\n ) : null}\n {!alarm.acknowledged ? (\n <button\n type=\"button\"\n style={{ ...actionButton, opacity: pending ? 0.6 : 1 }}\n disabled={pending !== null}\n aria-busy={pending === 'ack'}\n aria-label={`Acknowledge alarm ${alarm.alarmKey}`}\n onClick={() => run('ack', () => actions.acknowledgeAlarm(alarm.token))}\n >\n {pending === 'ack' ? '\u2026' : 'Ack'}\n </button>\n ) : null}\n <button\n type=\"button\"\n style={{ ...actionButton, opacity: pending ? 0.6 : 1 }}\n disabled={pending !== null}\n aria-busy={pending === 'clear'}\n aria-label={`Clear alarm ${alarm.alarmKey}`}\n onClick={() => run('clear', () => actions.clearAlarm(alarm.token))}\n >\n {pending === 'clear' ? '\u2026' : 'Clear'}\n </button>\n </span>\n );\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// The words the alarm table's device-type tab strip renders itself. The widget packages carry no\n// i18n runtime, so this is the one small per-language table, in the manner of fleet-strings.ts;\n// option labels are translated in the console's catalogs, not here.\n\nimport { viewerLocale } from '@devicechain/widget-sdk';\n\nexport interface AlarmStrings {\n // The tab strip's accessible name.\n tabsLabel: string;\n // The first tab: every alarm in scope.\n all: string;\n // The Type column header, shown on the All tab.\n deviceType: string;\n // A tab's badge, spoken: \"3 active alarms, worst Critical\".\n activeBadge: (count: number, worst: string) => string;\n // The severity words for the badge description.\n severity: Record<string, string>;\n // The severity breakdown (alarm-severity).\n breakdown: {\n // The list's accessible name, spoken before the counts.\n label: string;\n // The caption under the bar: \"7 alarms\".\n total: (count: number) => string;\n // The caption of a breakdown set to one device type: \"7 alarms on pump devices\".\n ofType: (count: number, deviceType: string) => string;\n // One list item, spoken: \"Critical: 2\".\n item: (severity: string, count: number) => string;\n loading: string;\n // The source returned no per-severity counts (it cannot count by severity).\n unsupported: string;\n // The count read failed.\n failed: string;\n };\n}\n\nexport const ALARM_STRINGS: Record<'en' | 'es', AlarmStrings> = {\n en: {\n tabsLabel: 'Alarms by device type',\n all: 'All',\n deviceType: 'Device type',\n activeBadge: (n, worst) => `${n} active ${n === 1 ? 'alarm' : 'alarms'}, worst ${worst}`,\n severity: { CRITICAL: 'Critical', MAJOR: 'Major', MINOR: 'Minor', WARNING: 'Warning', INDETERMINATE: 'Indeterminate' },\n breakdown: {\n label: 'Alarms by severity',\n total: (n) => `${n} ${n === 1 ? 'alarm' : 'alarms'}`,\n ofType: (n, type) => `${n} ${n === 1 ? 'alarm' : 'alarms'} on ${type} devices`,\n item: (severity, n) => `${severity}: ${n}`,\n loading: 'Loading alarms\u2026',\n unsupported: 'This data source cannot count alarms by severity.',\n failed: 'The alarm counts could not be read.',\n },\n },\n es: {\n tabsLabel: 'Alarmas por tipo de dispositivo',\n all: 'Todas',\n deviceType: 'Tipo de dispositivo',\n activeBadge: (n, worst) => `${n} ${n === 1 ? 'alarma activa' : 'alarmas activas'}, la peor: ${worst}`,\n severity: { CRITICAL: 'Cr\u00EDtica', MAJOR: 'Mayor', MINOR: 'Menor', WARNING: 'Advertencia', INDETERMINATE: 'Indeterminada' },\n breakdown: {\n label: 'Alarmas por severidad',\n total: (n) => `${n} ${n === 1 ? 'alarma' : 'alarmas'}`,\n ofType: (n, type) => `${n} ${n === 1 ? 'alarma' : 'alarmas'} en dispositivos de tipo ${type}`,\n item: (severity, n) => `${severity}: ${n}`,\n loading: 'Cargando alarmas\u2026',\n unsupported: 'Esta fuente de datos no puede contar alarmas por severidad.',\n failed: 'No se pudieron leer los recuentos de alarmas.',\n },\n },\n};\n\nexport function alarmStrings(locale: string | undefined = viewerLocale()): AlarmStrings {\n return locale?.toLowerCase().split('-')[0] === 'es' ? ALARM_STRINGS.es : ALARM_STRINGS.en;\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// The tab strip of an alarm table grouped by device type: \"All\", then one tab per type, each with an\n// attention badge. A small accessible tablist built here because the widget packages carry no UI kit.\n//\n// Roving tabindex, MANUAL activation: the arrow keys (and Home/End) move focus along the strip and\n// Enter or Space chooses the tab, because choosing one reads that tab's rows and walking past five\n// tabs should not read five tabs' worth of alarms. The strip scrolls sideways on a narrow board\n// rather than wrapping.\n//\n// The badge follows the platform rule that colour is for the abnormal: it appears only for a tab\n// with an ACTIVE alarm, its dot takes the colour of the worst severity, and an INDETERMINATE worst\n// (no alarm hue) stays neutral. The count and the worst severity are also text for assistive tech,\n// so the signal never rides on hue alone.\n\nimport type { AlarmTypeTab } from '@devicechain/dashboards';\nimport { css, fontVar, useSeverityColors } from '@devicechain/widget-sdk';\nimport { useRef, type CSSProperties, type KeyboardEvent } from 'react';\n\nimport { alarmStrings, type AlarmStrings } from '../alarm-strings';\nimport { severityColor } from './severity';\n\nexport interface AlarmTypeTabsProps {\n // Every tab to show, All excluded.\n tabs: readonly AlarmTypeTab[];\n selected: string | null;\n onSelect: (token: string | null) => void;\n // Ids the strip and its panel share, so each tab names the panel and the panel names its tab.\n idBase: string;\n}\n\nconst tabStyle = (active: boolean): CSSProperties => ({\n flex: '0 0 auto',\n display: 'inline-flex',\n alignItems: 'center',\n gap: 6,\n padding: '6px 10px',\n border: 'none',\n borderBottom: `2px solid ${active ? css('primary') : 'transparent'}`,\n background: 'none',\n font: 'inherit',\n fontSize: fontVar('md'),\n fontWeight: active ? 600 : 400,\n color: active ? css('foreground') : css('muted-foreground'),\n cursor: 'pointer',\n whiteSpace: 'nowrap',\n});\n\nexport const tabDomId = (idBase: string, token: string | null): string => `${idBase}-tab-${token ?? ''}`;\nexport const panelDomId = (idBase: string): string => `${idBase}-panel`;\n\nexport function AlarmTypeTabsStrip({ tabs, selected, onSelect, idBase }: AlarmTypeTabsProps) {\n const strings = alarmStrings();\n const severity = useSeverityColors();\n const refs = useRef(new Map<string | null, HTMLButtonElement>());\n const order: Array<string | null> = [null, ...tabs.map((t) => t.token)];\n // A selection the strip does not hold (a pinned list that dropped it) reads as All.\n const current = order.includes(selected) ? selected : null;\n\n const move = (event: KeyboardEvent, from: number): void => {\n const to =\n event.key === 'ArrowRight' ? (from + 1) % order.length\n : event.key === 'ArrowLeft' ? (from - 1 + order.length) % order.length\n : event.key === 'Home' ? 0\n : event.key === 'End' ? order.length - 1\n : -1;\n if (to < 0) return;\n event.preventDefault();\n refs.current.get(order[to])?.focus();\n };\n\n return (\n <div\n role=\"tablist\"\n aria-label={strings.tabsLabel}\n style={{\n display: 'flex',\n flexShrink: 0,\n overflowX: 'auto',\n overflowY: 'hidden',\n borderBottom: `1px solid ${css('border')}`,\n }}\n >\n {order.map((token, i) => {\n const tab = token === null ? undefined : tabs[i - 1];\n const active = token === current;\n return (\n <button\n key={token ?? '\\u0000all'}\n ref={(el) => {\n if (el) refs.current.set(token, el);\n else refs.current.delete(token);\n }}\n type=\"button\"\n role=\"tab\"\n id={tabDomId(idBase, token)}\n aria-selected={active}\n aria-controls={panelDomId(idBase)}\n tabIndex={active ? 0 : -1}\n onClick={() => onSelect(token)}\n onKeyDown={(e) => move(e, i)}\n style={tabStyle(active)}\n >\n {token === null ? strings.all : (tab?.name ?? token)}\n {tab && tab.active > 0 ? (\n <Badge tab={tab} colors={severity} strings={strings} />\n ) : null}\n </button>\n );\n })}\n </div>\n );\n}\n\nfunction Badge({\n tab,\n colors,\n strings,\n}: {\n tab: AlarmTypeTab;\n colors: ReturnType<typeof useSeverityColors>;\n strings: AlarmStrings;\n}) {\n const worst = tab.worstSeverity ?? 'INDETERMINATE';\n return (\n <span\n data-testid=\"alarm-tab-badge\"\n role=\"img\"\n aria-label={strings.activeBadge(tab.active, strings.severity[worst] ?? worst)}\n style={{\n display: 'inline-flex',\n alignItems: 'center',\n gap: 4,\n fontSize: fontVar('xs'),\n fontWeight: 600,\n fontVariantNumeric: 'tabular-nums',\n color: css('foreground'),\n }}\n >\n <span\n data-testid=\"alarm-tab-badge-dot\"\n aria-hidden=\"true\"\n style={{ width: 8, height: 8, borderRadius: '50%', background: severityColor(colors, worst), flexShrink: 0 }}\n />\n <span aria-hidden=\"true\">{tab.active}</span>\n </span>\n );\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// alarm-count \u2014 a KPI card of how many alarms match the widget's scope+filter\n// (ADR-041). The number is the server total (past the loaded page); its color is the\n// most severe among the CURRENTLY-LOADED alarms (the newest page), and the worst\n// severity is also spelled out as text/aria so the signal never reads on hue alone (a\n// color-blind or screen-reader viewer still gets it). At zero it stays muted.\n\nimport {\n type AlarmStreamState,\n css,\n fitValueFont,\n fitValueFontCss,\n FIT_MIN_FONT_PX,\n fontVar,\n optString,\n spaceVar,\n useSeverityColors,\n useStickyChars,\n useWidgetScale,\n WidgetFrame,\n type WidgetProps,\n} from '@devicechain/widget-sdk';\n\nimport { highestSeverity, severityColor, severityLabel } from './severity';\n\n// The caption under the count (11 px text plus its 6 px margin). It always shows -- it names\n// the worst severity -- so the count is fitted to what is left rather than pushing it out.\nexport const ALARM_COUNT_CAPTION_ROOM_PX = 20;\n\n// Rendered INSIDE the frame: the widget scale is the frame's content column, which is only in\n// context below the frame.\nfunction AlarmCountBody({ data }: { data: AlarmStreamState }) {\n const { size, scale } = useWidgetScale();\n const severity = useSeverityColors();\n const { total, alarms, loading } = data;\n\n const worst = highestSeverity(alarms.map((a) => a.severity));\n // At zero (or before any alarm is loaded) stay neutral; otherwise take the worst\n // loaded severity's color as the alert accent.\n const alerting = total > 0 && worst != null;\n const color = alerting ? severityColor(severity, worst) : css('muted-foreground');\n\n const display = loading && total === 0 ? '\u2014' : String(total);\n const chars = useStickyChars(display.length);\n const floor = Math.round(FIT_MIN_FONT_PX * scale);\n const measured = size.width > 0 && size.height > 0;\n const valueFont = measured\n ? fitValueFont(\n { width: size.width, height: Math.max(1, size.height - ALARM_COUNT_CAPTION_ROOM_PX * scale) },\n chars,\n { floor },\n )\n : // First frame: the same curve in cq units, resolved by the browser against the frame's\n // column (it does not take the caption's room off; the first measure does).\n fitValueFontCss(chars, { floor });\n const unit = total === 1 ? 'alarm' : 'alarms';\n const ariaLabel = alerting ? `${total} ${unit}, worst severity ${severityLabel(worst)}` : `${total} ${unit}`;\n\n return (\n <div\n style={{\n display: 'flex',\n flexDirection: 'column',\n justifyContent: 'safe center',\n alignItems: 'center',\n height: '100%',\n boxSizing: 'border-box',\n overflow: 'hidden',\n textAlign: 'center',\n }}\n >\n <div\n aria-label={ariaLabel}\n style={{\n fontSize: valueFont,\n fontWeight: 700,\n lineHeight: 1,\n whiteSpace: 'nowrap',\n fontVariantNumeric: 'tabular-nums',\n color,\n }}\n >\n {display}\n </div>\n <div\n style={{\n fontSize: fontVar('xs'),\n color: css('muted-foreground'),\n marginTop: spaceVar(1),\n textTransform: 'uppercase',\n letterSpacing: '0.05em',\n }}\n >\n <span>{unit}</span>\n {alerting ? <span>{` \u00B7 worst ${severityLabel(worst)}`}</span> : null}\n </div>\n </div>\n );\n}\n\nexport function AlarmCount({ widget, data }: WidgetProps<AlarmStreamState>) {\n return (\n <WidgetFrame title={optString(widget.options, 'title')}>\n <AlarmCountBody data={data} />\n </WidgetFrame>\n );\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// alarm-severity -- how many alarms are at each severity: a horizontal stacked bar over a legend\n// of counts. The counts are device-management's own per-severity counts, so they are exact however\n// many alarms there are. Unbound, they cover EVERY alarm the viewer can see, whatever raised it (a\n// device, an asset, an area, a customer, a group), and sum to an alarm count's total; set to a\n// device type, only the alarms on that type's devices, and the caption says so. The viewer's own\n// scope (a customer's alarms) is applied by the server.\n//\n// - EVERY severity is listed, most severe first, so the scale does not move as alarms come and\n// go. A severity with none is DIMMED, not hidden, and has no segment in the bar. Its TEXT is\n// dimmed by colour (muted-foreground, which keeps 4.5:1 on the card in both themes), never by\n// opacity (foreground at 45% falls to ~3.2:1 on a light card); only the decorative swatch fades;\n// - a severity with alarms takes its severity colour. INDETERMINATE (and any severity this\n// build does not know) takes the neutral muted colour, as everywhere else: no hue it has not\n// earned. There is no \"normal\" colour, and an empty scope is a calm, empty track;\n// - the bar is decoration (hidden from assistive tech). The legend is the content: a real list\n// whose items read \"Critical: 2\", so the colour is never the only carrier of the signal;\n// - a source that cannot count by severity says so rather than drawing a zero.\n//\n// DOM text, so the readability gate sees it. Lives in the alarm chunk; nothing here is eager.\n\nimport {\n css,\n fontVar,\n optString,\n spaceVar,\n useSeverityColors,\n WidgetFrame,\n type AlarmStreamState,\n type WidgetProps,\n} from '@devicechain/widget-sdk';\n\nimport { alarmStrings } from '../alarm-strings';\nimport { severityColor } from './severity';\n\n// The scale, most severe first. Everything a source returns that is not one of these (a future or\n// hand-edited severity) is counted with INDETERMINATE: counted, never dropped.\nexport const SEVERITY_SCALE = ['CRITICAL', 'MAJOR', 'MINOR', 'WARNING', 'INDETERMINATE'] as const;\nexport type ScaleSeverity = (typeof SEVERITY_SCALE)[number];\n\n// severityCounts folds the source's per-severity counts onto the scale. Zero severities are present\n// with a 0.\nexport function severityCounts(source: readonly { severity: string; count: number }[]): Record<ScaleSeverity, number> {\n const counts: Record<ScaleSeverity, number> = { CRITICAL: 0, MAJOR: 0, MINOR: 0, WARNING: 0, INDETERMINATE: 0 };\n for (const c of source) {\n if (!(c.count > 0)) continue;\n const key = (SEVERITY_SCALE as readonly string[]).includes(c.severity) ? (c.severity as ScaleSeverity) : 'INDETERMINATE';\n counts[key] += c.count;\n }\n return counts;\n}\n\nfunction Message({ title, text }: { title: string | undefined; text: string }) {\n return (\n <WidgetFrame title={title}>\n <div\n data-alarm-severity=\"message\"\n style={{\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n height: '100%',\n textAlign: 'center',\n fontSize: fontVar('sm'),\n color: css('muted-foreground'),\n }}\n >\n {text}\n </div>\n </WidgetFrame>\n );\n}\n\nexport function AlarmSeverity({ widget, data }: WidgetProps<AlarmStreamState>) {\n const strings = alarmStrings();\n const colors = useSeverityColors();\n const title = optString(widget.options, 'title');\n const deviceType = optString(widget.options, 'deviceType') || undefined;\n\n if (!data.severityCounts) {\n const text = data.loading\n ? strings.breakdown.loading\n : data.error\n ? strings.breakdown.failed\n : strings.breakdown.unsupported;\n return <Message title={title} text={text} />;\n }\n const counts = severityCounts(data.severityCounts);\n const total = SEVERITY_SCALE.reduce((n, s) => n + counts[s], 0);\n const words = (s: ScaleSeverity) => strings.severity[s] ?? s;\n // A type-scoped breakdown counts device alarms of that type only, and says so in its caption.\n const caption = deviceType ? strings.breakdown.ofType(total, deviceType) : strings.breakdown.total(total);\n const listLabel = `${strings.breakdown.label}, ${caption}`;\n\n return (\n <WidgetFrame title={title}>\n <div\n data-alarm-severity=\"breakdown\"\n data-total={total}\n style={{\n display: 'flex',\n flexDirection: 'column',\n justifyContent: 'safe center',\n gap: spaceVar(2),\n height: '100%',\n boxSizing: 'border-box',\n overflow: 'hidden',\n }}\n >\n <div\n aria-hidden=\"true\"\n data-alarm-severity-bar=\"\"\n style={{\n display: 'flex',\n flex: '0 0 auto',\n height: spaceVar(3),\n borderRadius: spaceVar(1),\n overflow: 'hidden',\n background: css('muted'),\n }}\n >\n {SEVERITY_SCALE.filter((s) => counts[s] > 0).map((s) => (\n <div key={s} data-segment={s} style={{ flex: `${counts[s]} 1 0`, minWidth: 2, background: severityColor(colors, s) }} />\n ))}\n </div>\n <div\n data-alarm-severity-total=\"\"\n style={{ flex: '0 0 auto', fontSize: fontVar('sm'), color: css('muted-foreground'), fontVariantNumeric: 'tabular-nums' }}\n >\n {caption}\n </div>\n <ul\n aria-label={listLabel}\n style={{\n listStyle: 'none',\n margin: 0,\n padding: 0,\n display: 'grid',\n gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 7.5rem), 1fr))',\n gap: `${spaceVar(1)} ${spaceVar(3)}`,\n minHeight: 0,\n overflow: 'hidden',\n }}\n >\n {SEVERITY_SCALE.map((s) => {\n const zero = counts[s] === 0;\n return (\n <li\n key={s}\n data-severity={s}\n data-zero={zero ? 'true' : undefined}\n aria-label={strings.breakdown.item(words(s), counts[s])}\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: spaceVar(2),\n minWidth: 0,\n fontSize: fontVar('md'),\n color: css(zero ? 'muted-foreground' : 'foreground'),\n }}\n >\n <span\n aria-hidden=\"true\"\n style={{\n flex: '0 0 auto',\n width: spaceVar(3),\n height: spaceVar(3),\n borderRadius: '50%',\n background: severityColor(colors, s),\n opacity: zero ? 0.35 : 1,\n }}\n />\n <span style={{ flex: '1 1 auto', minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>\n {words(s)}\n </span>\n <span style={{ flex: '0 0 auto', fontWeight: zero ? 400 : 700, fontVariantNumeric: 'tabular-nums' }}>{counts[s]}</span>\n </li>\n );\n })}\n </ul>\n </div>\n </WidgetFrame>\n );\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["css", "fontVar", "useSeverityColors", "css", "css", "Fragment", "jsx", "jsxs", "css", "fontVar", "useSeverityColors", "css", "fontVar", "optString", "useSeverityColors", "WidgetFrame", "jsx", "jsxs", "useSeverityColors", "css", "fontVar", "WidgetFrame", "optString", "css", "fontVar", "optString", "spaceVar", "useSeverityColors", "WidgetFrame", "jsx", "jsxs", "WidgetFrame", "fontVar", "css", "useSeverityColors", "optString", "spaceVar"]
7
+ }
@@ -1,7 +1,3 @@
1
- import {
2
- ACCESS_STRINGS
3
- } from "./chunk-BRH66T5F.js";
4
-
5
1
  // src/fleet-model.ts
6
2
  import {
7
3
  bucketSecondsFor
@@ -48,6 +44,7 @@ function buildFleet(i) {
48
44
  const best = newer(seeded?.latest[name], live?.get(name));
49
45
  if (best && inRange(best, best === seeded?.latest[name] ? seedSince : i.since)) latest[name] = best;
50
46
  }
47
+ const declared = ref.deviceType ? i.declared?.get(ref.deviceType) : void 0;
51
48
  const observed = new Set(seeded?.observed ?? []);
52
49
  for (const name of Object.keys(latest)) observed.add(name);
53
50
  return {
@@ -56,6 +53,7 @@ function buildFleet(i) {
56
53
  latest,
57
54
  seed: seed === void 0 ? "pending" : seed === "failed" ? "failed" : "ready",
58
55
  observed: [...observed],
56
+ ...declared ? { declared: [...declared] } : {},
59
57
  // A live reading of an unread name is its value: cellFor looks at `latest` first.
60
58
  ...seeded?.unread?.length ? { unread: seeded.unread } : {}
61
59
  };
@@ -82,10 +80,11 @@ function cellFor(member, measurement) {
82
80
  if (sample && typeof sample.value === "number" && Number.isFinite(sample.value)) {
83
81
  return { kind: "value", sample, value: sample.value };
84
82
  }
83
+ if (member.declared && !member.declared.includes(measurement)) return { kind: "not-reported" };
85
84
  if (member.seed === "pending") return { kind: "loading" };
86
85
  if (member.seed === "failed") return { kind: "failed" };
87
86
  if (member.unread?.includes(measurement)) return { kind: "unread" };
88
- return member.observed.length > 0 && !member.observed.includes(measurement) ? { kind: "not-reported" } : { kind: "no-data" };
87
+ return { kind: "no-data" };
89
88
  }
90
89
  function columnNames(named, members) {
91
90
  if (named.length > 0) return [...new Set(named)];
@@ -133,60 +132,6 @@ function deviceBars(members, measurement, sort) {
133
132
  }).map((x) => x.bar);
134
133
  }
135
134
 
136
- // src/fleet-strings.ts
137
- import { viewerLocale } from "@devicechain/widget-sdk";
138
- var FLEET_STRINGS = {
139
- en: {
140
- device: "Device",
141
- loading: "Loading",
142
- notReported: "does not report this",
143
- unread: "not loaded",
144
- noData: "no data in range",
145
- failed: "could not be read",
146
- unbound: "Choose a device group for this widget",
147
- forbidden: ACCESS_STRINGS.en.forbidden,
148
- unsupported: "This data source cannot list a fleet",
149
- noMembers: "No devices in this group",
150
- noMeasurement: "Choose the measurement to compare",
151
- oneDevice: "This widget shows one device, and it is bound to a group of devices whose readings it would mix together.",
152
- useFleetTable: "Bind it to one device, or show the group in a fleet table.",
153
- usePerDevice: "Set it to one bar per device to compare them, or bind it to one device.",
154
- stale: "stale",
155
- truncated: (shown, total) => `Showing ${shown} of ${total} devices`,
156
- range: (from, to, of) => `${from}\u2013${to} of ${of}`,
157
- previous: "Previous page",
158
- next: "Next page",
159
- sortBy: (column) => `Sort by ${column}`,
160
- drill: (device) => `Show ${device} in the linked widgets`
161
- },
162
- es: {
163
- device: "Dispositivo",
164
- loading: "Cargando",
165
- notReported: "no informa esta medici\xF3n",
166
- unread: "no cargado",
167
- noData: "sin datos en el rango",
168
- failed: "no se pudo leer",
169
- unbound: "Elija un grupo de dispositivos para este widget",
170
- forbidden: ACCESS_STRINGS.es.forbidden,
171
- unsupported: "Esta fuente de datos no puede listar una flota",
172
- noMembers: "No hay dispositivos en este grupo",
173
- noMeasurement: "Elija la medici\xF3n que comparar",
174
- oneDevice: "Este widget muestra un solo dispositivo y est\xE1 vinculado a un grupo de dispositivos cuyas lecturas mezclar\xEDa.",
175
- useFleetTable: "Vinc\xFAlelo a un dispositivo o muestre el grupo en una tabla de flota.",
176
- usePerDevice: "Config\xFArelo con una barra por dispositivo para compararlos, o vinc\xFAlelo a un dispositivo.",
177
- stale: "obsoleto",
178
- truncated: (shown, total) => `Mostrando ${shown} de ${total} dispositivos`,
179
- range: (from, to, of) => `${from}\u2013${to} de ${of}`,
180
- previous: "P\xE1gina anterior",
181
- next: "P\xE1gina siguiente",
182
- sortBy: (column) => `Ordenar por ${column}`,
183
- drill: (device) => `Mostrar ${device} en los widgets enlazados`
184
- }
185
- };
186
- function fleetStrings(locale = viewerLocale()) {
187
- return locale?.toLowerCase().split("-")[0] === "es" ? FLEET_STRINGS.es : FLEET_STRINGS.en;
188
- }
189
-
190
135
  export {
191
136
  seedWindow,
192
137
  buildFleet,
@@ -198,7 +143,6 @@ export {
198
143
  DEFAULT_PAGE_SIZE,
199
144
  pageCount,
200
145
  clampPage,
201
- deviceBars,
202
- fleetStrings
146
+ deviceBars
203
147
  };
204
- //# sourceMappingURL=chunk-WBKHYZU2.js.map
148
+ //# sourceMappingURL=chunk-62CHYCHI.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/fleet-model.ts"],
4
+ "sourcesContent": ["// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// The fleet widgets' state, decided apart from the drawing and from the hub: which reading a cell\n// shows, why one is empty, how the rows are ordered and paged. Pure, so each rule is a test.\n//\n// Everything is keyed by DEVICE and measurement together. The measurement channel keys its latest\n// values by name alone, so two thermostats reporting \"temperature\" overwrite each other there;\n// nothing in this file ever looks a value up by name without the device beside it.\n\nimport {\n bucketSecondsFor,\n type FleetMember,\n type FleetState,\n type HistoryWindow,\n type MeasurementSample,\n type MemberRoster,\n type MemberSeed,\n} from '@devicechain/dashboards';\n\nconst MINUTE_MS = 60_000;\n\n// The window a fleet's seed reads: whole minutes ending at the last minute boundary at or before\n// `at`, in buckets of bucketSecondsFor(span) (a multiple of 60, so the store answers from its\n// one-minute summaries). Whole minutes are what let widgets reading the same span on the same\n// board share one request per device (the hub keys in-flight reads by window), and what keep the\n// window from holding a reading from before the span. The end is the instant just before the\n// boundary, because the store's end is inclusive.\nexport function seedWindow(spanSeconds: number, at: number): HistoryWindow {\n const end = Math.floor(at / MINUTE_MS) * MINUTE_MS;\n const start = Math.ceil((end - spanSeconds * 1000) / MINUTE_MS) * MINUTE_MS;\n return {\n startTime: new Date(start).toISOString(),\n endTime: new Date(end - 1).toISOString(),\n intervalSeconds: bucketSecondsFor(Math.max(60, (end - start) / 1000)),\n };\n}\n\n// ---- Building the state ------------------------------------------------------\n\n// What the feed has gathered. `seeds` holds a settled seed per device (`'failed'` for a read that\n// did not come back); a device absent from it is still loading. `live` is every device's newest\n// sample per measurement name since the board opened.\nexport interface FleetInputs {\n roster: MemberRoster | null;\n seeds: ReadonlyMap<string, MemberSeed | 'failed'>;\n live: ReadonlyMap<string, ReadonlyMap<string, MeasurementSample>>;\n // What each device type's active profile declares, by type token; null is a settled \"nothing to\n // consult\". A type absent from it is not looked up yet. Either way its members' declaration is\n // unknown, which cellFor reads as declared.\n declared?: ReadonlyMap<string, readonly string[] | null>;\n // The start of the span (epoch ms): a reading older than this is not \"in range\".\n since: number;\n forbidden: boolean;\n}\n\nfunction timeOf(sample: MeasurementSample | undefined): number {\n if (!sample?.occurredTime) return Number.NaN;\n return Date.parse(sample.occurredTime);\n}\n\n// A sample with no readable time is taken as current: only a live sample can lack one, and it just\n// arrived.\nfunction inRange(sample: MeasurementSample, since: number): boolean {\n const at = timeOf(sample);\n return Number.isNaN(at) || at >= since;\n}\n\nfunction newer(a: MeasurementSample | undefined, b: MeasurementSample | undefined): MeasurementSample | undefined {\n if (!a) return b;\n if (!b) return a;\n const ta = timeOf(a);\n const tb = timeOf(b);\n if (Number.isNaN(ta)) return a;\n if (Number.isNaN(tb)) return b;\n return tb >= ta ? b : a;\n}\n\nexport function buildFleet(i: FleetInputs): FleetState {\n if (i.forbidden) return { status: 'forbidden', members: [], total: 0, truncated: false };\n if (!i.roster) return { status: 'loading', members: [], total: 0, truncated: false };\n if (i.roster.unbound) return { status: 'unbound', members: [], total: 0, truncated: false };\n\n // A seeded reading is stamped at the start of its minute (fleet-seed.ts, \"Ages\") and happened\n // somewhere in that minute, so it is in range while its minute is. Held to the exact instant, a\n // reading 59 minutes 45 seconds old on an hour's board was dropped as out of range.\n const seedSince = Math.floor(i.since / MINUTE_MS) * MINUTE_MS;\n const members = i.roster.members.map((ref): FleetMember => {\n const seed = i.seeds.get(ref.token);\n const seeded = seed && seed !== 'failed' ? seed : undefined;\n const live = i.live.get(ref.token);\n const latest: Record<string, MeasurementSample> = {};\n const names = new Set<string>([...Object.keys(seeded?.latest ?? {}), ...(live?.keys() ?? [])]);\n for (const name of names) {\n const best = newer(seeded?.latest[name], live?.get(name));\n if (best && inRange(best, best === seeded?.latest[name] ? seedSince : i.since)) latest[name] = best;\n }\n const declared = ref.deviceType ? i.declared?.get(ref.deviceType) : undefined;\n const observed = new Set<string>(seeded?.observed ?? []);\n for (const name of Object.keys(latest)) observed.add(name);\n return {\n token: ref.token,\n name: ref.name,\n latest,\n seed: seed === undefined ? 'pending' : seed === 'failed' ? 'failed' : 'ready',\n observed: [...observed],\n ...(declared ? { declared: [...declared] } : {}),\n // A live reading of an unread name is its value: cellFor looks at `latest` first.\n ...(seeded?.unread?.length ? { unread: seeded.unread } : {}),\n };\n });\n return {\n status: 'ready',\n members,\n total: i.roster.total,\n truncated: i.roster.total > members.length,\n };\n}\n\n// The newest reading anywhere in the fleet (epoch ms), or undefined when none carries a time.\nexport function newestMemberTime(fleet: FleetState): number | undefined {\n let newest: number | undefined;\n for (const m of fleet.members) {\n for (const sample of Object.values(m.latest)) {\n const at = timeOf(sample);\n if (!Number.isNaN(at) && (newest === undefined || at > newest)) newest = at;\n }\n }\n return newest;\n}\n\n// ---- Cells -------------------------------------------------------------------\n\n// What one (device, measurement) cell holds, and when it holds nothing, why.\n//\n// \"Does not report this\" is decided by what the device's profile DECLARES, never by what it has\n// sent: a sensor whose probe died while its heartbeat keeps arriving has sent other measurements\n// and not this one, and is exactly the device an operator needs to hear about. A device whose\n// declaration is not known (see FleetMember.declared) is taken to declare it.\nexport type Cell =\n | { kind: 'value'; sample: MeasurementSample; value: number }\n | { kind: 'loading' }\n // The device's history read failed and no live reading has arrived since.\n | { kind: 'failed' }\n // The device's active profile does not declare this measurement: it is not one the device reports.\n | { kind: 'not-reported' }\n // The device reported this in the span, but its newest reading was not loaded (MemberSeed.unread).\n | { kind: 'unread' }\n // The device reported nothing at all in the span.\n | { kind: 'no-data' };\n\nexport function cellFor(member: FleetMember, measurement: string): Cell {\n const sample = member.latest[measurement];\n if (sample && typeof sample.value === 'number' && Number.isFinite(sample.value)) {\n return { kind: 'value', sample, value: sample.value };\n }\n if (member.declared && !member.declared.includes(measurement)) return { kind: 'not-reported' };\n if (member.seed === 'pending') return { kind: 'loading' };\n if (member.seed === 'failed') return { kind: 'failed' };\n if (member.unread?.includes(measurement)) return { kind: 'unread' };\n return { kind: 'no-data' };\n}\n\n// The columns: the measurements the datasource names, in that order; with none named, every\n// measurement any member reported, alphabetically (stable across renders however samples arrive).\nexport function columnNames(named: readonly string[], members: readonly FleetMember[]): string[] {\n if (named.length > 0) return [...new Set(named)];\n const seen = new Set<string>();\n for (const m of members) for (const name of m.observed) seen.add(name);\n return [...seen].sort((a, b) => a.localeCompare(b));\n}\n\n// ---- Order and pages ------------------------------------------------------------\n\nexport interface SortKey {\n // A measurement name, or null for the device-name column.\n by: string | null;\n descending: boolean;\n}\n\nexport function deviceLabel(member: Pick<FleetMember, 'name' | 'token'>): string {\n return member.name && member.name !== '' ? member.name : member.token;\n}\n\nconst collator = new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' });\n\n// Ordered by the key, ties broken by device name then token, so the order never depends on the\n// order samples arrived in. A device with no value for the sorted measurement goes LAST in either\n// direction: \"largest first\" must not open with the devices that reported nothing.\nexport function sortMembers(members: readonly FleetMember[], key: SortKey): FleetMember[] {\n const byDevice = (a: FleetMember, b: FleetMember): number =>\n collator.compare(deviceLabel(a), deviceLabel(b)) || (a.token < b.token ? -1 : a.token > b.token ? 1 : 0);\n const sign = key.descending ? -1 : 1;\n return [...members].sort((a, b) => {\n if (key.by === null) return sign * byDevice(a, b);\n const ca = cellFor(a, key.by);\n const cb = cellFor(b, key.by);\n if (ca.kind !== 'value' || cb.kind !== 'value') {\n if (ca.kind === cb.kind) return byDevice(a, b);\n return ca.kind === 'value' ? -1 : cb.kind === 'value' ? 1 : byDevice(a, b);\n }\n return sign * (ca.value - cb.value) || byDevice(a, b);\n });\n}\n\nexport const DEFAULT_PAGE_SIZE = 20;\n\nexport function pageCount(rows: number, pageSize: number): number {\n return Math.max(1, Math.ceil(rows / Math.max(1, pageSize)));\n}\n\n// The page asked for, held inside what exists (a fleet that shrinks must not leave the viewer on a\n// page past the end).\nexport function clampPage(page: number, rows: number, pageSize: number): number {\n return Math.min(Math.max(0, page), pageCount(rows, pageSize) - 1);\n}\n\n// ---- Bars ---------------------------------------------------------------------------\n\n// One bar per member for one measurement, in the shape the bar chart already draws (category\n// items), ordered like the categorical charts order theirs. A device with no number keeps its\n// label with no bar and sorts after those that have one.\nexport interface DeviceBar {\n label: string;\n token: string;\n value: number | null;\n}\n\nexport function deviceBars(members: readonly FleetMember[], measurement: string, sort: 'none' | 'ascending' | 'descending'): DeviceBar[] {\n const bars = members.map((m): DeviceBar => {\n const cell = cellFor(m, measurement);\n return { label: deviceLabel(m), token: m.token, value: cell.kind === 'value' ? cell.value : null };\n });\n if (sort === 'none') return bars;\n const sign = sort === 'ascending' ? 1 : -1;\n return bars\n .map((bar, i) => ({ bar, i }))\n .sort((a, b) => {\n if (a.bar.value === null || b.bar.value === null) {\n return a.bar.value === b.bar.value ? a.i - b.i : a.bar.value === null ? 1 : -1;\n }\n return sign * (a.bar.value - b.bar.value) || a.i - b.i;\n })\n .map((x) => x.bar);\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,41 @@
1
+ // src/widgets/lazy-chunk.tsx
2
+ import { useEffect, useState } from "react";
3
+ import { optString, WidgetFrame } from "@devicechain/widget-sdk";
4
+ import { jsx } from "react/jsx-runtime";
5
+ function ChunkLoadingFrame({ widget }) {
6
+ return /* @__PURE__ */ jsx(WidgetFrame, { title: optString(widget.options, "title"), children: /* @__PURE__ */ jsx("div", { "data-chunk-loading": "", "aria-busy": "true", style: { width: "100%", height: "100%" } }) });
7
+ }
8
+ function lazyChunk(importer) {
9
+ let loaded;
10
+ let loading;
11
+ const load = () => loading ?? (loading = importer().then(
12
+ (m) => loaded = m,
13
+ (err) => {
14
+ loading = void 0;
15
+ throw err;
16
+ }
17
+ ));
18
+ function component(name, alsoPreload, Fallback) {
19
+ function LazyChunkWidget(props) {
20
+ const [mod, setMod] = useState(loaded);
21
+ const [error, setError] = useState();
22
+ useEffect(() => {
23
+ if (!mod) load().then(setMod, setError);
24
+ }, [mod]);
25
+ if (error !== void 0) throw error;
26
+ if (!mod) return Fallback ? /* @__PURE__ */ jsx(Fallback, { ...props }) : null;
27
+ const Component = mod[name];
28
+ return /* @__PURE__ */ jsx(Component, { ...props });
29
+ }
30
+ LazyChunkWidget.displayName = `Lazy(${name})`;
31
+ LazyChunkWidget.preload = alsoPreload ? () => Promise.all([load(), alsoPreload()]).then(([m]) => m) : load;
32
+ return LazyChunkWidget;
33
+ }
34
+ return { load, component };
35
+ }
36
+
37
+ export {
38
+ ChunkLoadingFrame,
39
+ lazyChunk
40
+ };
41
+ //# sourceMappingURL=chunk-BMFQOTYG.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/widgets/lazy-chunk.tsx"],
4
+ "sourcesContent": ["// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// The lazy registration shared by the widget families that ship as their own chunk. A family's\n// descriptors stay eager (the catalog, the editor and the validators read them), and each\n// registration's component loads the family's chunk on first mount, together, from one fetch.\n//\n// Until the chunk arrives the widget renders nothing inside its box (or the `Fallback` its\n// registration names: a feed uses ChunkLoadingFrame), unless the host awaited preloadWidgets\n// (preload.ts) for the board first: then the chunk is already here and the first frame is complete. A chunk that fails to load is THROWN, so the widget's error boundary shows\n// the failure: an empty box that looks like a widget with nothing to say is the outcome this must\n// never produce. The failure is NOT remembered: the next mount (the boundary's \"Reset widget\")\n// fetches the chunk again, so a blip on a wall display's network is not a widget that stays broken\n// until the page is reloaded. React.lazy remembers it, which is why the feeds load through here too.\n//\n// The `import()` itself stays in each family's own module, as a literal: that is what makes the\n// bundler split the chunk, and it is what the dist-shape guard looks for.\n\nimport { useEffect, useState, type ComponentType } from 'react';\nimport { optString, WidgetFrame, type MeasurementStreamState, type WidgetProps } from '@devicechain/widget-sdk';\nimport type { WidgetInstance } from '@devicechain/dashboards';\n\n// What a feed chunk shows while it is in flight: the widget's frame and title, marked busy, rather\n// than an empty box. A feed is chosen by the widget's OPTIONS (a chart set to one line per device),\n// which preloadWidgets cannot see -- it is handed types -- so its first frame is often this one.\nexport function ChunkLoadingFrame({ widget }: { widget: WidgetInstance }) {\n return (\n <WidgetFrame title={optString(widget.options, 'title')}>\n <div data-chunk-loading=\"\" aria-busy=\"true\" style={{ width: '100%', height: '100%' }} />\n </WidgetFrame>\n );\n}\n\nexport function lazyChunk<M extends Record<string, unknown>, D = MeasurementStreamState>(importer: () => Promise<M>) {\n let loaded: M | undefined;\n let loading: Promise<M> | undefined;\n\n // Memoized: every widget of the family on a board shares one fetch. Once it has resolved,\n // `loaded` lets a later mount render synchronously, with no empty first frame. A rejection is\n // forgotten, so the next load fetches again; only a mount asks, so a chunk that keeps failing\n // is never retried in a loop.\n const load = (): Promise<M> =>\n (loading ??= importer().then(\n (m) => (loaded = m),\n (err: unknown) => {\n loading = undefined;\n throw err;\n },\n ));\n\n // `P` is the data a given component takes when it differs from the family's default `D` (a chunk\n // that holds widgets of two channels). `Props` is for a component that is not a widget at all.\n // `alsoPreload` is code the component needs beyond the chunk that is itself loaded on demand (the\n // chart engine, for a chart in the chunk): preloadWidgets fetches both, in parallel.\n function component<P = D, Props extends object = WidgetProps<P>>(\n name: keyof M & string,\n alsoPreload?: () => Promise<unknown>,\n // Drawn until the chunk arrives (nothing, when absent).\n Fallback?: ComponentType<Props>,\n ) {\n function LazyChunkWidget(props: Props) {\n const [mod, setMod] = useState(loaded);\n const [error, setError] = useState<unknown>();\n useEffect(() => {\n if (!mod) load().then(setMod, setError);\n }, [mod]);\n if (error !== undefined) throw error;\n if (!mod) return Fallback ? <Fallback {...props} /> : null;\n const Component = mod[name] as ComponentType<Props>;\n return <Component {...props} />;\n }\n LazyChunkWidget.displayName = `Lazy(${name})`;\n // What preloadWidgets calls, so a host that awaits it renders this widget on its first frame.\n LazyChunkWidget.preload = alsoPreload ? () => Promise.all([load(), alsoPreload()]).then(([m]) => m) : load;\n return LazyChunkWidget;\n }\n\n return { load, component };\n}\n"],
5
+ "mappings": ";AAkBA,SAAS,WAAW,gBAAoC;AACxD,SAAS,WAAW,mBAAkE;AAShF;AAHC,SAAS,kBAAkB,EAAE,OAAO,GAA+B;AACxE,SACE,oBAAC,eAAY,OAAO,UAAU,OAAO,SAAS,OAAO,GACnD,8BAAC,SAAI,sBAAmB,IAAG,aAAU,QAAO,OAAO,EAAE,OAAO,QAAQ,QAAQ,OAAO,GAAG,GACxF;AAEJ;AAEO,SAAS,UAAyE,UAA4B;AACnH,MAAI;AACJ,MAAI;AAMJ,QAAM,OAAO,MACV,sBAAY,SAAS,EAAE;AAAA,IACtB,CAAC,MAAO,SAAS;AAAA,IACjB,CAAC,QAAiB;AAChB,gBAAU;AACV,YAAM;AAAA,IACR;AAAA,EACF;AAMF,WAAS,UACP,MACA,aAEA,UACA;AACA,aAAS,gBAAgB,OAAc;AACrC,YAAM,CAAC,KAAK,MAAM,IAAI,SAAS,MAAM;AACrC,YAAM,CAAC,OAAO,QAAQ,IAAI,SAAkB;AAC5C,gBAAU,MAAM;AACd,YAAI,CAAC,IAAK,MAAK,EAAE,KAAK,QAAQ,QAAQ;AAAA,MACxC,GAAG,CAAC,GAAG,CAAC;AACR,UAAI,UAAU,OAAW,OAAM;AAC/B,UAAI,CAAC,IAAK,QAAO,WAAW,oBAAC,YAAU,GAAG,OAAO,IAAK;AACtD,YAAM,YAAY,IAAI,IAAI;AAC1B,aAAO,oBAAC,aAAW,GAAG,OAAO;AAAA,IAC/B;AACA,oBAAgB,cAAc,QAAQ,IAAI;AAE1C,oBAAgB,UAAU,cAAc,MAAM,QAAQ,IAAI,CAAC,KAAK,GAAG,YAAY,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI;AACtG,WAAO;AAAA,EACT;AAEA,SAAO,EAAE,MAAM,UAAU;AAC3B;",
6
+ "names": []
7
+ }
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  isValidSpanSeconds
4
4
  } from "@devicechain/dashboards";
5
- import { optNumber, optString } from "@devicechain/widget-sdk";
5
+ import { optBoolean, optNumber, optString } from "@devicechain/widget-sdk";
6
6
  function numberValue(binding, options) {
7
7
  if (!binding) return void 0;
8
8
  let value;
@@ -33,6 +33,13 @@ function membersMode(descriptor, widget) {
33
33
  if (members.const === true) return true;
34
34
  return members.option !== void 0 && optString(widget.options, members.option) === members.equals;
35
35
  }
36
+ function measurementIsMembersOnly(descriptor) {
37
+ const members = policy(descriptor).members;
38
+ return members !== void 0 && members.const !== true && members.option !== void 0;
39
+ }
40
+ function presenceMode(descriptor) {
41
+ return policy(descriptor).presence?.const === true;
42
+ }
36
43
  function parseAcknowledged(value) {
37
44
  if (value === "true") return true;
38
45
  if (value === "false") return false;
@@ -40,26 +47,43 @@ function parseAcknowledged(value) {
40
47
  }
41
48
  function alarmSubscription(descriptor, widget) {
42
49
  const p = policy(descriptor);
50
+ const deviceType = stringValue(p.deviceType, widget.options);
43
51
  return {
44
52
  datasource: widget.datasource,
45
53
  state: stringValue(p.state, widget.options),
46
54
  severity: stringValue(p.severity, widget.options),
47
55
  acknowledged: parseAcknowledged(stringValue(p.acknowledged, widget.options)),
48
- pageSize: numberValue(p.pageSize, widget.options) ?? 1
56
+ pageSize: numberValue(p.pageSize, widget.options) ?? 1,
57
+ // A widget that declares `binding.countBySeverity` reads counts, not rows; `binding.deviceType`
58
+ // narrows them to one device type (a blank option is no type).
59
+ ...p.countBySeverity?.const === true ? { countBySeverity: true } : {},
60
+ ...deviceType ? { deviceType } : {}
49
61
  };
50
62
  }
63
+ function groupsAlarmsByDeviceType(descriptor, widget) {
64
+ const groupBy = policy(descriptor).groupBy;
65
+ return groupBy?.option !== void 0 && optString(widget.options, groupBy.option) === groupBy.equals;
66
+ }
51
67
  function commandSubscription(descriptor, widget) {
52
68
  return {
53
69
  datasource: widget.datasource,
54
70
  pageSize: numberValue(policy(descriptor).pageSize, widget.options) ?? 1
55
71
  };
56
72
  }
73
+ function wantsEventMarkers(descriptor, widget) {
74
+ const declared = descriptor.options;
75
+ return declared.markAlarms !== void 0 && optBoolean(widget.options, "markAlarms") === true || declared.markCommands !== void 0 && optBoolean(widget.options, "markCommands") === true;
76
+ }
57
77
 
58
78
  export {
59
79
  measurementWindow,
60
80
  measurementSpan,
61
81
  membersMode,
82
+ measurementIsMembersOnly,
83
+ presenceMode,
62
84
  alarmSubscription,
63
- commandSubscription
85
+ groupsAlarmsByDeviceType,
86
+ commandSubscription,
87
+ wantsEventMarkers
64
88
  };
65
- //# sourceMappingURL=chunk-2D7JNKFE.js.map
89
+ //# sourceMappingURL=chunk-JFH4QMLA.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 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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",
6
+ "names": []
7
+ }
@@ -0,0 +1,84 @@
1
+ import {
2
+ ACCESS_STRINGS
3
+ } from "./chunk-BRH66T5F.js";
4
+
5
+ // src/fleet-strings.ts
6
+ import { viewerLocale } from "@devicechain/widget-sdk";
7
+ var FLEET_STRINGS = {
8
+ en: {
9
+ device: "Device",
10
+ loading: "Loading",
11
+ notReported: "does not report this",
12
+ unread: "not loaded",
13
+ noData: "no data in range",
14
+ failed: "could not be read",
15
+ unbound: "Choose a device group for this widget",
16
+ forbidden: ACCESS_STRINGS.en.forbidden,
17
+ unsupported: "This data source cannot list a fleet",
18
+ noMembers: "No devices in this group",
19
+ noMeasurement: "Choose the measurement to compare",
20
+ oneDevice: "This widget shows one device, and it is bound to a group of devices whose readings it would mix together.",
21
+ useFleetTable: "Bind it to one device, or show the group in a fleet table.",
22
+ usePerDevice: "Set it to one bar per device to compare them, or bind it to one device.",
23
+ useLinePerDevice: "Set it to one line per device to compare them, or bind it to one device.",
24
+ stale: "stale",
25
+ noMeasurementCount: "Choose the measurement to watch",
26
+ noBands: "Add thresholds to say what needs attention",
27
+ countOf: (total) => `of ${total}`,
28
+ needAttention: "need attention",
29
+ devices: "devices",
30
+ notReporting: (n) => `${n} not reporting`,
31
+ notMeasured: (n) => n === 1 ? "1 does not report this" : `${n} do not report this`,
32
+ noneMeasured: "No device in this group reports this",
33
+ noneMeasuredShown: (shown, total) => `None of the ${shown} of ${total} devices read reports this`,
34
+ notRead: (n) => `${n} could not be read`,
35
+ truncated: (shown, total) => `Showing ${shown} of ${total} devices`,
36
+ range: (from, to, of) => `${from}\u2013${to} of ${of}`,
37
+ previous: "Previous page",
38
+ next: "Next page",
39
+ sortBy: (column) => `Sort by ${column}`,
40
+ drill: (device) => `Show ${device} in the linked widgets`
41
+ },
42
+ es: {
43
+ device: "Dispositivo",
44
+ loading: "Cargando",
45
+ notReported: "no informa esta medici\xF3n",
46
+ unread: "no cargado",
47
+ noData: "sin datos en el rango",
48
+ failed: "no se pudo leer",
49
+ unbound: "Elija un grupo de dispositivos para este widget",
50
+ forbidden: ACCESS_STRINGS.es.forbidden,
51
+ unsupported: "Esta fuente de datos no puede listar una flota",
52
+ noMembers: "No hay dispositivos en este grupo",
53
+ noMeasurement: "Elija la medici\xF3n que comparar",
54
+ oneDevice: "Este widget muestra un solo dispositivo y est\xE1 vinculado a un grupo de dispositivos cuyas lecturas mezclar\xEDa.",
55
+ useFleetTable: "Vinc\xFAlelo a un dispositivo o muestre el grupo en una tabla de flota.",
56
+ usePerDevice: "Config\xFArelo con una barra por dispositivo para compararlos, o vinc\xFAlelo a un dispositivo.",
57
+ useLinePerDevice: "Config\xFArelo con una l\xEDnea por dispositivo para compararlos, o vinc\xFAlelo a un dispositivo.",
58
+ stale: "obsoleto",
59
+ noMeasurementCount: "Elija la medici\xF3n que vigilar",
60
+ noBands: "A\xF1ada umbrales para indicar qu\xE9 requiere atenci\xF3n",
61
+ countOf: (total) => `de ${total}`,
62
+ needAttention: "requieren atenci\xF3n",
63
+ devices: "dispositivos",
64
+ notReporting: (n) => `${n} sin informar`,
65
+ notMeasured: (n) => n === 1 ? "1 no informa esta medici\xF3n" : `${n} no informan esta medici\xF3n`,
66
+ noneMeasured: "Ning\xFAn dispositivo de este grupo informa esta medici\xF3n",
67
+ noneMeasuredShown: (shown, total) => `Ninguno de los ${shown} de ${total} dispositivos le\xEDdos informa esta medici\xF3n`,
68
+ notRead: (n) => `${n} sin leer`,
69
+ truncated: (shown, total) => `Mostrando ${shown} de ${total} dispositivos`,
70
+ range: (from, to, of) => `${from}\u2013${to} de ${of}`,
71
+ previous: "P\xE1gina anterior",
72
+ next: "P\xE1gina siguiente",
73
+ sortBy: (column) => `Ordenar por ${column}`,
74
+ drill: (device) => `Mostrar ${device} en los widgets enlazados`
75
+ }
76
+ };
77
+ function fleetStrings(locale = viewerLocale()) {
78
+ return locale?.toLowerCase().split("-")[0] === "es" ? FLEET_STRINGS.es : FLEET_STRINGS.en;
79
+ }
80
+
81
+ export {
82
+ fleetStrings
83
+ };
84
+ //# sourceMappingURL=chunk-QMGKB6J3.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/fleet-strings.ts"],
4
+ "sourcesContent": ["// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// The fixed words the fleet table and the per-device bar chart render themselves: the device\n// column, the reason a cell holds no number, the truncation and paging captions. The widget\n// packages carry no i18n runtime, so this is the one small per-language table (modelled on\n// aggregate-strings.ts); option labels are translated in the console's catalogs, not here.\n\nimport { viewerLocale } from '@devicechain/widget-sdk';\nimport { ACCESS_STRINGS } from './access-strings';\n\nexport interface FleetStrings {\n device: string;\n loading: string;\n // The reason a cell holds no number (D-5): each is a different fact about the device.\n notReported: string;\n // It reported this, but its newest reading was not loaded: there IS data.\n unread: string;\n noData: string;\n failed: string;\n // Whole-widget states.\n unbound: string;\n forbidden: string;\n unsupported: string;\n noMembers: string;\n noMeasurement: string;\n // A widget that shows one device, bound to a group: refused, with where the group can be shown.\n oneDevice: string;\n useFleetTable: string;\n usePerDevice: string;\n useLinePerDevice: string;\n stale: string;\n // The fleet count card.\n noMeasurementCount: string;\n noBands: string;\n countOf: (total: number) => string;\n needAttention: string;\n devices: string;\n notReporting: (n: number) => string;\n // Devices whose profile does not declare the measurement: outside the count, said apart.\n notMeasured: (n: number) => string;\n // No device of the group has a profile that declares the measurement.\n noneMeasured: string;\n // The same over a truncated group: only the devices read were asked, so it must not speak for the rest.\n noneMeasuredShown: (shown: number, total: number) => string;\n notRead: (n: number) => string;\n // \"Showing 500 of 812 devices\": a fleet past what is read must say so.\n truncated: (shown: number, total: number) => string;\n range: (from: number, to: number, of: number) => string;\n previous: string;\n next: string;\n sortBy: (column: string) => string;\n drill: (device: string) => string;\n}\n\nexport const FLEET_STRINGS: Record<'en' | 'es', FleetStrings> = {\n en: {\n device: 'Device',\n loading: 'Loading',\n notReported: 'does not report this',\n unread: 'not loaded',\n noData: 'no data in range',\n failed: 'could not be read',\n unbound: 'Choose a device group for this widget',\n forbidden: ACCESS_STRINGS.en.forbidden,\n unsupported: 'This data source cannot list a fleet',\n noMembers: 'No devices in this group',\n noMeasurement: 'Choose the measurement to compare',\n oneDevice: 'This widget shows one device, and it is bound to a group of devices whose readings it would mix together.',\n useFleetTable: 'Bind it to one device, or show the group in a fleet table.',\n usePerDevice: 'Set it to one bar per device to compare them, or bind it to one device.',\n useLinePerDevice: 'Set it to one line per device to compare them, or bind it to one device.',\n stale: 'stale',\n noMeasurementCount: 'Choose the measurement to watch',\n noBands: 'Add thresholds to say what needs attention',\n countOf: (total) => `of ${total}`,\n needAttention: 'need attention',\n devices: 'devices',\n notReporting: (n) => `${n} not reporting`,\n notMeasured: (n) => (n === 1 ? '1 does not report this' : `${n} do not report this`),\n noneMeasured: 'No device in this group reports this',\n noneMeasuredShown: (shown, total) => `None of the ${shown} of ${total} devices read reports this`,\n notRead: (n) => `${n} could not be read`,\n truncated: (shown, total) => `Showing ${shown} of ${total} devices`,\n range: (from, to, of) => `${from}\u2013${to} of ${of}`,\n previous: 'Previous page',\n next: 'Next page',\n sortBy: (column) => `Sort by ${column}`,\n drill: (device) => `Show ${device} in the linked widgets`,\n },\n es: {\n device: 'Dispositivo',\n loading: 'Cargando',\n notReported: 'no informa esta medici\u00F3n',\n unread: 'no cargado',\n noData: 'sin datos en el rango',\n failed: 'no se pudo leer',\n unbound: 'Elija un grupo de dispositivos para este widget',\n forbidden: ACCESS_STRINGS.es.forbidden,\n unsupported: 'Esta fuente de datos no puede listar una flota',\n noMembers: 'No hay dispositivos en este grupo',\n noMeasurement: 'Elija la medici\u00F3n que comparar',\n oneDevice: 'Este widget muestra un solo dispositivo y est\u00E1 vinculado a un grupo de dispositivos cuyas lecturas mezclar\u00EDa.',\n useFleetTable: 'Vinc\u00FAlelo a un dispositivo o muestre el grupo en una tabla de flota.',\n usePerDevice: 'Config\u00FArelo con una barra por dispositivo para compararlos, o vinc\u00FAlelo a un dispositivo.',\n useLinePerDevice: 'Config\u00FArelo con una l\u00EDnea por dispositivo para compararlos, o vinc\u00FAlelo a un dispositivo.',\n stale: 'obsoleto',\n noMeasurementCount: 'Elija la medici\u00F3n que vigilar',\n noBands: 'A\u00F1ada umbrales para indicar qu\u00E9 requiere atenci\u00F3n',\n countOf: (total) => `de ${total}`,\n needAttention: 'requieren atenci\u00F3n',\n devices: 'dispositivos',\n notReporting: (n) => `${n} sin informar`,\n notMeasured: (n) => (n === 1 ? '1 no informa esta medici\u00F3n' : `${n} no informan esta medici\u00F3n`),\n noneMeasured: 'Ning\u00FAn dispositivo de este grupo informa esta medici\u00F3n',\n noneMeasuredShown: (shown, total) => `Ninguno de los ${shown} de ${total} dispositivos le\u00EDdos informa esta medici\u00F3n`,\n notRead: (n) => `${n} sin leer`,\n truncated: (shown, total) => `Mostrando ${shown} de ${total} dispositivos`,\n range: (from, to, of) => `${from}\u2013${to} de ${of}`,\n previous: 'P\u00E1gina anterior',\n next: 'P\u00E1gina siguiente',\n sortBy: (column) => `Ordenar por ${column}`,\n drill: (device) => `Mostrar ${device} en los widgets enlazados`,\n },\n};\n\nexport function fleetStrings(locale: string | undefined = viewerLocale()): FleetStrings {\n return locale?.toLowerCase().split('-')[0] === 'es' ? FLEET_STRINGS.es : FLEET_STRINGS.en;\n}\n"],
5
+ "mappings": ";;;;;AAQA,SAAS,oBAAoB;AA+CtB,IAAM,gBAAmD;AAAA,EAC9D,IAAI;AAAA,IACF,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,aAAa;AAAA,IACb,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,WAAW,eAAe,GAAG;AAAA,IAC7B,aAAa;AAAA,IACb,WAAW;AAAA,IACX,eAAe;AAAA,IACf,WAAW;AAAA,IACX,eAAe;AAAA,IACf,cAAc;AAAA,IACd,kBAAkB;AAAA,IAClB,OAAO;AAAA,IACP,oBAAoB;AAAA,IACpB,SAAS;AAAA,IACT,SAAS,CAAC,UAAU,MAAM,KAAK;AAAA,IAC/B,eAAe;AAAA,IACf,SAAS;AAAA,IACT,cAAc,CAAC,MAAM,GAAG,CAAC;AAAA,IACzB,aAAa,CAAC,MAAO,MAAM,IAAI,2BAA2B,GAAG,CAAC;AAAA,IAC9D,cAAc;AAAA,IACd,mBAAmB,CAAC,OAAO,UAAU,eAAe,KAAK,OAAO,KAAK;AAAA,IACrE,SAAS,CAAC,MAAM,GAAG,CAAC;AAAA,IACpB,WAAW,CAAC,OAAO,UAAU,WAAW,KAAK,OAAO,KAAK;AAAA,IACzD,OAAO,CAAC,MAAM,IAAI,OAAO,GAAG,IAAI,SAAI,EAAE,OAAO,EAAE;AAAA,IAC/C,UAAU;AAAA,IACV,MAAM;AAAA,IACN,QAAQ,CAAC,WAAW,WAAW,MAAM;AAAA,IACrC,OAAO,CAAC,WAAW,QAAQ,MAAM;AAAA,EACnC;AAAA,EACA,IAAI;AAAA,IACF,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,aAAa;AAAA,IACb,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,WAAW,eAAe,GAAG;AAAA,IAC7B,aAAa;AAAA,IACb,WAAW;AAAA,IACX,eAAe;AAAA,IACf,WAAW;AAAA,IACX,eAAe;AAAA,IACf,cAAc;AAAA,IACd,kBAAkB;AAAA,IAClB,OAAO;AAAA,IACP,oBAAoB;AAAA,IACpB,SAAS;AAAA,IACT,SAAS,CAAC,UAAU,MAAM,KAAK;AAAA,IAC/B,eAAe;AAAA,IACf,SAAS;AAAA,IACT,cAAc,CAAC,MAAM,GAAG,CAAC;AAAA,IACzB,aAAa,CAAC,MAAO,MAAM,IAAI,kCAA+B,GAAG,CAAC;AAAA,IAClE,cAAc;AAAA,IACd,mBAAmB,CAAC,OAAO,UAAU,kBAAkB,KAAK,OAAO,KAAK;AAAA,IACxE,SAAS,CAAC,MAAM,GAAG,CAAC;AAAA,IACpB,WAAW,CAAC,OAAO,UAAU,aAAa,KAAK,OAAO,KAAK;AAAA,IAC3D,OAAO,CAAC,MAAM,IAAI,OAAO,GAAG,IAAI,SAAI,EAAE,OAAO,EAAE;AAAA,IAC/C,UAAU;AAAA,IACV,MAAM;AAAA,IACN,QAAQ,CAAC,WAAW,eAAe,MAAM;AAAA,IACzC,OAAO,CAAC,WAAW,WAAW,MAAM;AAAA,EACtC;AACF;AAEO,SAAS,aAAa,SAA6B,aAAa,GAAiB;AACtF,SAAO,QAAQ,YAAY,EAAE,MAAM,GAAG,EAAE,CAAC,MAAM,OAAO,cAAc,KAAK,cAAc;AACzF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,19 @@
1
+ // src/use-held.ts
2
+ import { useState } from "react";
3
+ function useHeld(value, hold, ready) {
4
+ const [held, setHeld] = useState(void 0);
5
+ if (!hold) {
6
+ if (held !== void 0) setHeld(void 0);
7
+ return value;
8
+ }
9
+ if (held === void 0 || ready(held.value) !== ready(value)) {
10
+ setHeld({ value });
11
+ return value;
12
+ }
13
+ return held.value;
14
+ }
15
+
16
+ export {
17
+ useHeld
18
+ };
19
+ //# sourceMappingURL=chunk-XNEBAY4J.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/use-held.ts"],
4
+ "sourcesContent": ["// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\nimport { useState } from 'react';\n\n// useHeld returns `value` live, and while `hold` is true the value it had when the hold began:\n// the whole-snapshot channels (alarms, locations) deliver a complete state each time, so freezing\n// one is just not looking at the newer ones. Nothing is lost -- the source keeps updating, and\n// releasing the hold shows its current state at once.\n//\n// `ready` says whether a value is worth freezing on. A board that OPENS paused (a deep link) holds\n// its first value, which is the \"loading\" one; freezing on that would leave the widget loading for\n// as long as the pause lasts. So the hold takes the first value that is ready, and keeps it.\n//\n// A held snapshot that is ready while the live one is loading again means the stream was re-pointed:\n// the alarm and location streams read as loading in the very render that carries a new subscription\n// (their snapshots are tagged with it, so this holds even for a source that answers inside\n// subscribe), or when the host replaces the hub itself (new authorities), which re-subscribes the same\n// way -- and at no other time: a dropped socket, a failed query and the reconnect's re-query all\n// answer the SAME subscription, ready, and leave the hold alone. Selection stays live while a board\n// is paused. That hold belongs to the previous entity, so it is dropped and the new entity's first\n// ready snapshot is held instead. Kept, the alarm rows and map markers of the device the board was\n// paused on would stay under the new device's name. So the hold follows the live value whenever the\n// two disagree on being ready.\nexport function useHeld<T>(value: T, hold: boolean, ready: (v: T) => boolean): T {\n const [held, setHeld] = useState<{ value: T } | undefined>(undefined);\n if (!hold) {\n if (held !== undefined) setHeld(undefined);\n return value;\n }\n if (held === undefined || ready(held.value) !== ready(value)) {\n setHeld({ value });\n return value;\n }\n return held.value;\n}\n"],
5
+ "mappings": ";AAGA,SAAS,gBAAgB;AAqBlB,SAAS,QAAW,OAAU,MAAe,OAA6B;AAC/E,QAAM,CAAC,MAAM,OAAO,IAAI,SAAmC,MAAS;AACpE,MAAI,CAAC,MAAM;AACT,QAAI,SAAS,OAAW,SAAQ,MAAS;AACzC,WAAO;AAAA,EACT;AACA,MAAI,SAAS,UAAa,MAAM,KAAK,KAAK,MAAM,MAAM,KAAK,GAAG;AAC5D,YAAQ,EAAE,MAAM,CAAC;AACjB,WAAO;AAAA,EACT;AACA,SAAO,KAAK;AACd;",
6
+ "names": []
7
+ }