@devicechain/widgets 0.26.0 → 0.27.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/aggregate-feed.d.ts +5 -2
- package/dist/binding-policy.d.ts +1 -0
- package/dist/chunks/{aggregate-feed-7JBQIYJE.js → aggregate-feed-HCF5FYIJ.js} +14 -4
- package/dist/chunks/aggregate-feed-HCF5FYIJ.js.map +7 -0
- package/dist/chunks/{aggregate-widgets-72HMPMBU.js → aggregate-widgets-EVJNAA26.js} +5 -5
- package/dist/chunks/{chunk-NYHNQ5VR.js → chunk-GUCEPNGM.js} +6 -2
- package/dist/chunks/{chunk-NYHNQ5VR.js.map → chunk-GUCEPNGM.js.map} +2 -2
- package/dist/chunks/chunk-QHTO5ZEM.js +35 -0
- package/dist/chunks/chunk-QHTO5ZEM.js.map +7 -0
- package/dist/chunks/{compare-feed-5OLZHFRL.js → compare-feed-MLHG7YFN.js} +8 -8
- package/dist/chunks/{event-markers-feed-B3MVFYUT.js → event-markers-feed-DMRW5GV5.js} +2 -2
- package/dist/chunks/{fleet-feed-D4T3LC25.js → fleet-feed-AKJ7QIMP.js} +5 -5
- package/dist/chunks/{history-widgets-MBR4YRBV.js → history-widgets-DZBMNMEX.js} +2 -2
- package/dist/chunks/{presence-widgets-M5VGG3OO.js → presence-widgets-7OGANOLB.js} +2 -2
- package/dist/host-context.d.ts +2 -0
- package/dist/host-context.js +13 -0
- package/dist/host-context.js.map +7 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +153 -173
- package/dist/index.js.map +4 -4
- package/package.json +11 -7
- package/dist/chunks/aggregate-feed-7JBQIYJE.js.map +0 -7
- /package/dist/chunks/{aggregate-widgets-72HMPMBU.js.map → aggregate-widgets-EVJNAA26.js.map} +0 -0
- /package/dist/chunks/{compare-feed-5OLZHFRL.js.map → compare-feed-MLHG7YFN.js.map} +0 -0
- /package/dist/chunks/{event-markers-feed-B3MVFYUT.js.map → event-markers-feed-DMRW5GV5.js.map} +0 -0
- /package/dist/chunks/{fleet-feed-D4T3LC25.js.map → fleet-feed-AKJ7QIMP.js.map} +0 -0
- /package/dist/chunks/{history-widgets-MBR4YRBV.js.map → history-widgets-DZBMNMEX.js.map} +0 -0
- /package/dist/chunks/{presence-widgets-M5VGG3OO.js.map → presence-widgets-7OGANOLB.js.map} +0 -0
package/dist/aggregate-feed.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type WidgetActions, type WidgetDataSource, type WidgetDescriptor, type WidgetInstance } from '@devicechain/dashboards';
|
|
1
|
+
import { type MeasurementSample, type WidgetActions, type WidgetDataSource, type WidgetDescriptor, type WidgetInstance } from '@devicechain/dashboards';
|
|
2
2
|
import { type ComponentType } from 'react';
|
|
3
3
|
export interface AggregateFeedProps {
|
|
4
4
|
hub: WidgetDataSource;
|
|
@@ -6,9 +6,12 @@ export interface AggregateFeedProps {
|
|
|
6
6
|
descriptor: WidgetDescriptor;
|
|
7
7
|
Component: ComponentType<any>;
|
|
8
8
|
actions: WidgetActions | undefined;
|
|
9
|
+
initialSamples?: MeasurementSample[];
|
|
10
|
+
initialSamplesAsOf?: number;
|
|
11
|
+
onReconnect?: () => void;
|
|
9
12
|
ErrorFrame: ComponentType<{
|
|
10
13
|
widget: WidgetInstance;
|
|
11
14
|
error: unknown;
|
|
12
15
|
}>;
|
|
13
16
|
}
|
|
14
|
-
export default function AggregateFeed({ hub, widget, descriptor, Component, actions, ErrorFrame }: AggregateFeedProps): import("react").JSX.Element;
|
|
17
|
+
export default function AggregateFeed({ hub, widget, descriptor, Component, actions, ErrorFrame, initialSamples, initialSamplesAsOf, onReconnect }: AggregateFeedProps): import("react").JSX.Element;
|
package/dist/binding-policy.d.ts
CHANGED
|
@@ -9,3 +9,4 @@ export declare function alarmSubscription(descriptor: WidgetDescriptor, widget:
|
|
|
9
9
|
export declare function groupsAlarmsByDeviceType(descriptor: WidgetDescriptor, widget: WidgetInstance): boolean;
|
|
10
10
|
export declare function commandSubscription(descriptor: WidgetDescriptor, widget: WidgetInstance): CommandSubscription;
|
|
11
11
|
export declare function wantsEventMarkers(descriptor: WidgetDescriptor, widget: WidgetInstance): boolean;
|
|
12
|
+
export declare function takesHistorySeed(descriptor: WidgetDescriptor, widget: WidgetInstance): boolean;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
measurementSpan
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-GUCEPNGM.js";
|
|
4
4
|
|
|
5
5
|
// src/aggregate-feed.tsx
|
|
6
6
|
import {
|
|
@@ -90,14 +90,24 @@ function aggregateWindow(spanSeconds, at, bucketSeconds) {
|
|
|
90
90
|
// src/aggregate-feed.tsx
|
|
91
91
|
import { jsx } from "react/jsx-runtime";
|
|
92
92
|
var LOADING = { status: "loading" };
|
|
93
|
-
function AggregateFeed({ hub, widget, descriptor, Component, actions, ErrorFrame }) {
|
|
93
|
+
function AggregateFeed({ hub, widget, descriptor, Component, actions, ErrorFrame, initialSamples, initialSamplesAsOf, onReconnect }) {
|
|
94
94
|
const range = useWidgetTimeRange();
|
|
95
95
|
const now = useMemo(() => hub.now ? () => hub.now() : Date.now, [hub]);
|
|
96
96
|
const spanSeconds = measurementSpan(descriptor, widget, range?.spanSeconds) ?? DEFAULT_TIME_RANGE_SECONDS;
|
|
97
97
|
const pausedAt = range?.paused ? range.pausedAt : void 0;
|
|
98
98
|
const ds = widget.datasource;
|
|
99
99
|
const [resumes, resumed] = useReducer((n) => n + 1, 0);
|
|
100
|
-
const stream = useMeasurementStream(hub, ds, {
|
|
100
|
+
const stream = useMeasurementStream(hub, ds, {
|
|
101
|
+
window: 1,
|
|
102
|
+
pausedAt,
|
|
103
|
+
now,
|
|
104
|
+
initialSamples,
|
|
105
|
+
initialSamplesAsOf,
|
|
106
|
+
onReconnect: () => {
|
|
107
|
+
resumed();
|
|
108
|
+
onReconnect?.();
|
|
109
|
+
}
|
|
110
|
+
});
|
|
101
111
|
const trend = optBoolean(widget.options, "sparkline");
|
|
102
112
|
const key = JSON.stringify([ds ?? null, spanSeconds, pausedAt ?? null, trend]);
|
|
103
113
|
const supported = hub.bucketMeasurements !== void 0;
|
|
@@ -158,4 +168,4 @@ function AggregateFeed({ hub, widget, descriptor, Component, actions, ErrorFrame
|
|
|
158
168
|
export {
|
|
159
169
|
AggregateFeed as default
|
|
160
170
|
};
|
|
161
|
-
//# sourceMappingURL=aggregate-feed-
|
|
171
|
+
//# sourceMappingURL=aggregate-feed-HCF5FYIJ.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/aggregate-feed.tsx", "../../src/aggregate-fold.ts"],
|
|
4
|
+
"sourcesContent": ["// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// The data feed of an aggregate widget: the exact aggregates of its measurements over its span,\n// plus the newest live reading of each. Loaded on first use (connected-widget.tsx imports it\n// dynamically), so a board with no aggregate widget never downloads it.\n//\n// The aggregates are folded from the source's own buckets (WidgetDataSource.bucketMeasurements: each\n// carries the exact count, min, max and sum), never from the chart seed: a seed is a series of bucket\n// AVERAGES, and a minimum or maximum cannot be recovered from those. A source that produces no\n// buckets is an explained state, not a guess.\n//\n// - the span is the widget's pinned one, else the board's (DEFAULT_TIME_RANGE_SECONDS off a board);\n// - the window is whole minutes (aggregateWindow), so it holds no reading from before the span;\n// - live, the read ends at the last minute and repeats on the boundaries of aggregateRefreshMs\n// (one chart bucket of the span: a minute for an hour, 42 minutes for a week), so every card on\n// the board reads at the same moment and the hub serves identical reads once; paused, it ends\n// at the freeze instant and is read once, so the number on screen does not move;\n// - a card with `sparkline` set reads the SAME window in the span's chart buckets\n// (bucketSecondsFor) instead of one, and keeps each bucket as a trend point; the figure is\n// folded from those buckets and is the same number either way;\n// - a hidden tab does not read; it reads once when it is shown again;\n// - a live feed that resumes after an interruption reads again at once, so the figures take in\n// the readings sent during the outage (and a read that failed during it stops saying so) without\n// waiting for the next refresh, which on a week's span is 42 minutes away;\n// - a read for a different selector, span or freeze instant is not shown under the new one: a\n// result is tagged with the key it was read for, and a mismatch reads as loading;\n// - a card showing the latest reading ('last') is also handed the newest stored bucket by the board\n// (initialSamples), a minute's mean standing in until a live reading arrives and never over one.\n\nimport {\n bucketSecondsFor,\n DEFAULT_TIME_RANGE_SECONDS,\n type AggregateResult,\n type AggregateStreamState,\n type MeasurementSample,\n type WidgetActions,\n type WidgetDataSource,\n type WidgetDescriptor,\n type WidgetInstance,\n} from '@devicechain/dashboards';\nimport { optBoolean, useMeasurementStream, useWidgetTimeRange } from '@devicechain/widget-sdk';\nimport { useEffect, useMemo, useReducer, useState, type ComponentType } from 'react';\n\nimport { aggregateBuckets, aggregateRefreshMs, aggregateSeries, aggregateWindow } from './aggregate-fold';\nimport { measurementSpan } from './binding-policy';\n\nconst LOADING: AggregateResult = { status: 'loading' };\n\nexport interface AggregateFeedProps {\n hub: WidgetDataSource;\n widget: WidgetInstance;\n descriptor: WidgetDescriptor;\n // The widget's component, and the host's pane for a stream that failed (before any figure).\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n Component: ComponentType<any>;\n actions: WidgetActions | undefined;\n // The stored latest reading of a card that shows it (see seedKey in dashboard-renderer.tsx).\n initialSamples?: MeasurementSample[];\n initialSamplesAsOf?: number;\n onReconnect?: () => void;\n ErrorFrame: ComponentType<{ widget: WidgetInstance; error: unknown }>;\n}\n\nexport default function AggregateFeed({ hub, widget, descriptor, Component, actions, ErrorFrame, initialSamples, initialSamplesAsOf, onReconnect }: AggregateFeedProps) {\n const range = useWidgetTimeRange();\n const now = useMemo(() => (hub.now ? () => hub.now!() : Date.now), [hub]);\n const spanSeconds = measurementSpan(descriptor, widget, range?.spanSeconds) ?? DEFAULT_TIME_RANGE_SECONDS;\n const pausedAt = range?.paused ? range.pausedAt : undefined;\n const ds = widget.datasource;\n\n // The newest live reading per measurement. It holds one sample: only `latest` is read, and the\n // pause keeps it from moving.\n const [resumes, resumed] = useReducer((n: number) => n + 1, 0);\n const stream = useMeasurementStream(hub, ds, {\n window: 1,\n pausedAt,\n now,\n initialSamples,\n initialSamplesAsOf,\n onReconnect: () => {\n resumed();\n onReconnect?.();\n },\n });\n\n const trend = optBoolean(widget.options, 'sparkline');\n const key = JSON.stringify([ds ?? null, spanSeconds, pausedAt ?? null, trend]);\n const supported = hub.bucketMeasurements !== undefined;\n const [tagged, setTagged] = useState<{ key: string; result: AggregateResult }>({ key, result: LOADING });\n\n useEffect(() => {\n if (!supported || !ds) return;\n let current = true;\n // Reads overlap (a boundary, a tab shown, a reconnect); only the NEWEST one's outcome is applied.\n let latest = 0;\n const run = (): void => {\n const mine = ++latest;\n const { window, spanSeconds: covered } = aggregateWindow(spanSeconds, pausedAt ?? now(), trend ? bucketSecondsFor(spanSeconds) : undefined);\n // Folded inside the chain, so a bucket that cannot be trusted fails the read like a network error.\n hub.bucketMeasurements!(ds, window).then((buckets) => ({ byName: aggregateBuckets(buckets), buckets })).then(\n ({ byName, buckets }) => {\n if (!current || mine !== latest) return;\n const { startTime, endTime } = window;\n const series = trend ? { series: aggregateSeries(buckets) } : {};\n setTagged({ key, result: { status: 'ready', byName, ...series, startTime, endTime, spanSeconds: covered } });\n },\n (err: unknown) => {\n if (!current || mine !== latest) return;\n const message = err instanceof Error ? err.message : String(err);\n setTagged({ key, result: { status: 'unavailable', reason: 'failed', message } });\n },\n );\n };\n run();\n // Paused, the window is fixed at the freeze instant: nothing new can land in it.\n if (pausedAt !== undefined) return () => void (current = false);\n const hidden = () => document.hidden;\n const every = aggregateRefreshMs(spanSeconds);\n let timer: ReturnType<typeof setTimeout>;\n const schedule = (): void => {\n timer = setTimeout(\n () => {\n if (!hidden()) run();\n schedule();\n },\n every - (now() % every),\n );\n };\n schedule();\n const shown = (): void => {\n if (!hidden()) run();\n };\n document.addEventListener('visibilitychange', shown);\n return () => {\n current = false;\n clearTimeout(timer);\n document.removeEventListener('visibilitychange', shown);\n };\n // `ds` is read through `key` (value identity), not by reference.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [hub, supported, key, resumes]);\n\n let aggregates: AggregateResult;\n if (!supported) aggregates = { status: 'unavailable', reason: 'unsupported' };\n else if (!ds) aggregates = { status: 'ready', byName: {}, startTime: '', endTime: '', spanSeconds };\n else aggregates = tagged.key === key ? tagged.result : LOADING;\n\n const data: AggregateStreamState = { aggregates, latest: stream.latest, error: stream.error };\n return data.error ? <ErrorFrame widget={widget} error={data.error} /> : <Component widget={widget} data={data} actions={actions} />;\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// Aggregates over a span, folded from the backend's time buckets. Each bucket carries the exact\n// count, minimum, maximum and sum of the readings in it, so the span's aggregates are exact too:\n// the min of the mins, the max of the maxes, the sum of the sums, the count of the counts, and a\n// mean that is the total sum over the total count (which is the count-weighted mean of the\n// buckets' averages, without ever reading an average).\n//\n// \uD83D\uDD34 A MINIMUM OR MAXIMUM IS NEVER DERIVED FROM AVERAGES. A bucket's average hides its extremes, so\n// the \"max of the avgs\" is a plausible number that is not the maximum. A bucket that holds\n// readings but lacks a field the aggregate needs is therefore a loud failure here, never a\n// quiet substitution.\n\nimport {\n bucketSecondsFor,\n type AggregateTrendPoint,\n type HistoryWindow,\n type MeasurementAggregate,\n type MeasurementBucket,\n} from '@devicechain/dashboards';\n\nexport class AggregateFieldsError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'AggregateFieldsError';\n }\n}\n\n// aggregateBuckets folds the buckets (any number of devices) into one aggregate per measurement\n// name. A name with no readings in any bucket is absent from the result, not zero.\nexport function aggregateBuckets(buckets: readonly MeasurementBucket[]): Record<string, MeasurementAggregate> {\n const out: Record<string, MeasurementAggregate> = {};\n for (const b of buckets) {\n if (typeof b.count !== 'number') {\n throw new AggregateFieldsError(`the bucket at ${b.bucketStart} for \"${b.name}\" carries no reading count`);\n }\n if (b.count === 0) continue;\n const { min, max, sum } = b;\n if (min == null || max == null || sum == null) {\n const missing = [min == null && 'min', max == null && 'max', sum == null && 'sum'].filter(Boolean);\n throw new AggregateFieldsError(\n `the bucket at ${b.bucketStart} for \"${b.name}\" holds ${b.count} readings but no ${missing.join(', ')}`,\n );\n }\n const a = out[b.name];\n if (!a) out[b.name] = { count: b.count, min, max, sum, avg: sum / b.count };\n else {\n a.count += b.count;\n a.sum += sum;\n a.min = Math.min(a.min, min);\n a.max = Math.max(a.max, max);\n a.avg = a.sum / a.count;\n }\n }\n return out;\n}\n\n// aggregateSeries lays the same buckets out as a trend: per measurement name, one point per bucket\n// that holds readings, oldest first. Buckets of several devices at one instant merge exactly as\n// aggregateBuckets merges them. Call it only on buckets aggregateBuckets accepted (it does not\n// re-validate), so a bucket that cannot be trusted has already failed the read.\nexport function aggregateSeries(buckets: readonly MeasurementBucket[]): Record<string, AggregateTrendPoint[]> {\n const byName = new Map<string, Map<string, AggregateTrendPoint>>();\n for (const b of buckets) {\n if (!b.count || b.min == null || b.max == null || b.sum == null) continue;\n let points = byName.get(b.name);\n if (!points) byName.set(b.name, (points = new Map()));\n const at = points.get(b.bucketStart);\n if (!at) {\n points.set(b.bucketStart, { bucketStart: b.bucketStart, count: b.count, min: b.min, max: b.max, sum: b.sum, avg: b.sum / b.count });\n } else {\n at.count += b.count;\n at.sum += b.sum;\n at.min = Math.min(at.min, b.min);\n at.max = Math.max(at.max, b.max);\n at.avg = at.sum / at.count;\n }\n }\n const out: Record<string, AggregateTrendPoint[]> = {};\n for (const [name, points] of byName) {\n out[name] = [...points.values()].sort((a, b) => Date.parse(a.bucketStart) - Date.parse(b.bucketStart));\n }\n return out;\n}\n\n// ---- The window a stat reads ------------------------------------------------------------------\n\nconst MINUTE_MS = 60_000;\n\n// aggregateRefreshMs is how often a live stat reads again: one bucket of the span's chart\n// (bucketSecondsFor), so never more often than once a minute. Finer is wasted -- the window it\n// reads moves in whole minutes (aggregateWindow) -- and a 7-day figure re-read every few seconds\n// costs the event store a scan of a week of summaries to move by a few seconds' readings.\nexport function aggregateRefreshMs(spanSeconds: number): number {\n return bucketSecondsFor(spanSeconds) * 1000;\n}\n\nexport interface AggregateWindow {\n window: HistoryWindow;\n // The length the window actually covers, in seconds: the span, or less when the span is not a\n // whole number of minutes. This, not the span asked for, is what the card says it covers.\n spanSeconds: number;\n}\n\n// aggregateWindow is the window a stat over `spanSeconds` reads at `at` (epoch ms): whole minutes,\n// ending at the last minute boundary at or before `at`.\n//\n// \uD83D\uDD34 Whole minutes because the event store answers from one-minute summaries, and a summary is\n// read whole: a window starting mid-minute would also count the readings from that minute's\n// start, BEFORE the span, so \"Maximum \u00B7 1h\" could be the maximum of an hour and a minute. Both\n// edges fall on minute boundaries instead, and the end is asked for as the instant just before\n// its boundary (the store's end is inclusive), so the summaries read are exactly the window's.\n// The start rounds UP, so a span that is not a whole number of minutes covers less, never more,\n// and says so (`spanSeconds`).\n//\n// Live cards read again on the boundaries of aggregateRefreshMs (a whole number of minutes), so\n// every card of one span ends its window on the same boundary: cards that read the same\n// measurement ask the same question at the same moment, and the hub answers them with one read.\n// One bucket wide: the card needs only the totals, so the store returns one or two rows per\n// measurement rather than a chart's worth.\n//\n// `bucketSeconds` asks for the window in finer buckets (a trend needs a point per bucket); the\n// totals fold identically from any number of buckets, so the figure is the same either way. Unset\n// the window is one bucket wide, as above.\nexport function aggregateWindow(spanSeconds: number, at: number, bucketSeconds?: number): AggregateWindow {\n const end = Math.floor(at / MINUTE_MS) * MINUTE_MS;\n const start = Math.ceil((end - spanSeconds * 1000) / MINUTE_MS) * MINUTE_MS;\n const covered = (end - start) / 1000;\n return {\n window: {\n startTime: new Date(start).toISOString(),\n endTime: new Date(end - 1).toISOString(),\n intervalSeconds: Math.max(60, bucketSeconds === undefined ? covered : Math.min(covered, bucketSeconds)),\n },\n spanSeconds: covered,\n };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["bucketSecondsFor", "bucketSecondsFor"]
|
|
7
|
+
}
|
|
@@ -6,20 +6,20 @@ import {
|
|
|
6
6
|
import {
|
|
7
7
|
fleetStrings
|
|
8
8
|
} from "./chunk-QMGKB6J3.js";
|
|
9
|
+
import "./chunk-BRH66T5F.js";
|
|
9
10
|
import {
|
|
10
11
|
Explained
|
|
11
12
|
} from "./chunk-AF4M5OXQ.js";
|
|
12
13
|
import {
|
|
13
14
|
NoReading
|
|
14
15
|
} from "./chunk-JVC6JYUR.js";
|
|
15
|
-
import {
|
|
16
|
-
fleetExport
|
|
17
|
-
} from "./chunk-VTGKDO44.js";
|
|
18
|
-
import "./chunk-BRH66T5F.js";
|
|
19
16
|
import {
|
|
20
17
|
LevelEmpty,
|
|
21
18
|
LevelReadout
|
|
22
19
|
} from "./chunk-EQOWBDS7.js";
|
|
20
|
+
import {
|
|
21
|
+
fleetExport
|
|
22
|
+
} from "./chunk-VTGKDO44.js";
|
|
23
23
|
import "./chunk-XJ7UF2CP.js";
|
|
24
24
|
import {
|
|
25
25
|
DEFAULT_PAGE_SIZE,
|
|
@@ -1129,4 +1129,4 @@ export {
|
|
|
1129
1129
|
PieChart,
|
|
1130
1130
|
StatCard
|
|
1131
1131
|
};
|
|
1132
|
-
//# sourceMappingURL=aggregate-widgets-
|
|
1132
|
+
//# sourceMappingURL=aggregate-widgets-EVJNAA26.js.map
|
|
@@ -77,6 +77,9 @@ function wantsEventMarkers(descriptor, widget) {
|
|
|
77
77
|
const declared = descriptor.options;
|
|
78
78
|
return declared.markAlarms !== void 0 && optBoolean(widget.options, "markAlarms") === true || declared.markCommands !== void 0 && optBoolean(widget.options, "markCommands") === true;
|
|
79
79
|
}
|
|
80
|
+
function takesHistorySeed(descriptor, widget) {
|
|
81
|
+
return descriptor.channel === "measurement" || descriptor.channel === "aggregate" && optString(widget.options, "stat") === "last";
|
|
82
|
+
}
|
|
80
83
|
|
|
81
84
|
export {
|
|
82
85
|
measurementWindow,
|
|
@@ -88,6 +91,7 @@ export {
|
|
|
88
91
|
alarmSubscription,
|
|
89
92
|
groupsAlarmsByDeviceType,
|
|
90
93
|
commandSubscription,
|
|
91
|
-
wantsEventMarkers
|
|
94
|
+
wantsEventMarkers,
|
|
95
|
+
takesHistorySeed
|
|
92
96
|
};
|
|
93
|
-
//# sourceMappingURL=chunk-
|
|
97
|
+
//# sourceMappingURL=chunk-GUCEPNGM.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// 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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",
|
|
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\n// Whether a widget takes the stored history as a seed. Every measurement widget does. An aggregate\n// widget reads its own aggregates and takes none, except a card showing the LATEST reading: the live\n// stream only delivers what arrives after the board opens, so a device that reports every few\n// minutes would leave it saying there is no data while its readings are stored.\nexport function takesHistorySeed(descriptor: WidgetDescriptor, widget: WidgetInstance): boolean {\n return descriptor.channel === 'measurement' || (descriptor.channel === 'aggregate' && optString(widget.options, 'stat') === 'last');\n}\n"],
|
|
5
|
+
"mappings": ";AAUA;AAAA,EACE;AAAA,OAKK;AAEP,SAAS,YAAY,WAAW,iBAAiB;AAgBjD,SAAS,YAAY,SAAoC,SAAwD;AAC/G,MAAI,CAAC,QAAS,QAAO;AACrB,MAAI;AACJ,MAAI,OAAO,QAAQ,UAAU,SAAU,SAAQ,QAAQ;AAAA,WAC9C,QAAQ,WAAW,OAAW,SAAQ,UAAU,SAAS,QAAQ,MAAM,KAAK,QAAQ;AAC7F,MAAI,UAAU,OAAW,QAAO;AAChC,SAAO,QAAQ,QAAQ,SAAY,KAAK,IAAI,QAAQ,KAAK,KAAK,IAAI;AACpE;AAEA,SAAS,YAAY,SAAoC,SAAwD;AAC/G,MAAI,CAAC,WAAW,QAAQ,WAAW,OAAW,QAAO;AACrD,SAAO,UAAU,SAAS,QAAQ,MAAM,KAAK,QAAQ;AACvD;AAEA,SAAS,OAAO,YAAuD;AACrE,SAAO,WAAW;AACpB;AAGO,SAAS,kBAAkB,YAA8B,QAA4C;AAC1G,SAAO,YAAY,OAAO,UAAU,EAAE,QAAqC,OAAO,OAAO;AAC3F;AAQO,SAAS,gBACd,YACA,QACA,WACoB;AACpB,QAAM,UAAU,OAAO,UAAU,EAAE;AACnC,MAAI,CAAC,QAAS,QAAO;AACrB,QAAM,SAAS,YAAY,EAAE,GAAG,SAAS,KAAK,OAAU,GAAG,OAAO,OAAO;AACzE,SAAO,mBAAmB,MAAM,IAAI,SAAS;AAC/C;AAMO,SAAS,YAAY,YAA8B,QAAiC;AACzF,QAAM,UAAU,OAAO,UAAU,EAAE;AACnC,MAAI,CAAC,QAAS,QAAO;AACrB,MAAI,QAAQ,UAAU,KAAM,QAAO;AACnC,SAAO,QAAQ,WAAW,UAAa,UAAU,OAAO,SAAS,QAAQ,MAAM,MAAM,QAAQ;AAC/F;AAOO,SAAS,yBAAyB,YAAuC;AAC9E,QAAM,UAAU,OAAO,UAAU,EAAE;AACnC,SAAO,YAAY,UAAa,QAAQ,UAAU,QAAQ,QAAQ,WAAW;AAC/E;AAKO,SAAS,aAAa,YAAuC;AAClE,SAAQ,OAAO,UAAU,EAAE,UAA8C,UAAU;AACrF;AAKO,SAAS,YAAY,YAAuC;AACjE,SAAQ,OAAO,UAAU,EAAE,SAA6C,UAAU;AACpF;AAGA,SAAS,kBAAkB,OAAgD;AACzE,MAAI,UAAU,OAAQ,QAAO;AAC7B,MAAI,UAAU,QAAS,QAAO;AAC9B,SAAO;AACT;AAIO,SAAS,kBAAkB,YAA8B,QAA2C;AACzG,QAAM,IAAI,OAAO,UAAU;AAC3B,QAAM,aAAa,YAAY,EAAE,YAAyC,OAAO,OAAO;AACxF,SAAO;AAAA,IACL,YAAY,OAAO;AAAA,IACnB,OAAO,YAAY,EAAE,OAAoC,OAAO,OAAO;AAAA,IACvE,UAAU,YAAY,EAAE,UAAuC,OAAO,OAAO;AAAA,IAC7E,cAAc,kBAAkB,YAAY,EAAE,cAA2C,OAAO,OAAO,CAAC;AAAA,IACxG,UAAU,YAAY,EAAE,UAAuC,OAAO,OAAO,KAAK;AAAA;AAAA;AAAA,IAGlF,GAAK,EAAE,iBAAqD,UAAU,OAAO,EAAE,iBAAiB,KAAK,IAAI,CAAC;AAAA,IAC1G,GAAI,aAAa,EAAE,WAAW,IAAI,CAAC;AAAA,EACrC;AACF;AAIO,SAAS,yBAAyB,YAA8B,QAAiC;AACtG,QAAM,UAAU,OAAO,UAAU,EAAE;AACnC,SAAO,SAAS,WAAW,UAAa,UAAU,OAAO,SAAS,QAAQ,MAAM,MAAM,QAAQ;AAChG;AAGO,SAAS,oBAAoB,YAA8B,QAA6C;AAC7G,SAAO;AAAA,IACL,YAAY,OAAO;AAAA,IACnB,UAAU,YAAY,OAAO,UAAU,EAAE,UAAuC,OAAO,OAAO,KAAK;AAAA,EACrG;AACF;AAKO,SAAS,kBAAkB,YAA8B,QAAiC;AAC/F,QAAM,WAAW,WAAW;AAC5B,SACG,SAAS,eAAe,UAAa,WAAW,OAAO,SAAS,YAAY,MAAM,QAClF,SAAS,iBAAiB,UAAa,WAAW,OAAO,SAAS,cAAc,MAAM;AAE3F;AAMO,SAAS,iBAAiB,YAA8B,QAAiC;AAC9F,SAAO,WAAW,YAAY,iBAAkB,WAAW,YAAY,eAAe,UAAU,OAAO,SAAS,MAAM,MAAM;AAC9H;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// src/basemap-context.tsx
|
|
2
|
+
import { createContext, useContext } from "react";
|
|
3
|
+
import { jsx } from "react/jsx-runtime";
|
|
4
|
+
var TenantBasemapContext = createContext(null);
|
|
5
|
+
function TenantBasemapProvider({
|
|
6
|
+
basemap,
|
|
7
|
+
children
|
|
8
|
+
}) {
|
|
9
|
+
return /* @__PURE__ */ jsx(TenantBasemapContext.Provider, { value: basemap, children });
|
|
10
|
+
}
|
|
11
|
+
function useTenantBasemap() {
|
|
12
|
+
return useContext(TenantBasemapContext);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// src/map-runtime-context.tsx
|
|
16
|
+
import { createContext as createContext2, useContext as useContext2 } from "react";
|
|
17
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
18
|
+
var MapRuntimeContext = createContext2(null);
|
|
19
|
+
function MapRuntimeProvider({
|
|
20
|
+
runtime,
|
|
21
|
+
children
|
|
22
|
+
}) {
|
|
23
|
+
return /* @__PURE__ */ jsx2(MapRuntimeContext.Provider, { value: runtime, children });
|
|
24
|
+
}
|
|
25
|
+
function useMapRuntime() {
|
|
26
|
+
return useContext2(MapRuntimeContext);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export {
|
|
30
|
+
TenantBasemapProvider,
|
|
31
|
+
useTenantBasemap,
|
|
32
|
+
MapRuntimeProvider,
|
|
33
|
+
useMapRuntime
|
|
34
|
+
};
|
|
35
|
+
//# sourceMappingURL=chunk-QHTO5ZEM.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/basemap-context.tsx", "../../src/map-runtime-context.tsx"],
|
|
4
|
+
"sourcesContent": ["// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// The tenant basemap, threaded to map widgets as ambient environment (ADR-079).\n//\n// \uD83D\uDD34 Why context rather than a new field on WidgetProps. A widget is contractually a\n// pure function of (widget, data): the props carry what the widget SHOWS. A tenant's\n// tile source is not data \u2014 it is environment, like the theme \u2014 and putting it in the\n// props would change the contract for all fifteen widgets to serve one of them.\n//\n// The default is null, deliberately. A host that installs no provider (widgetlab, the\n// synthetic preview, every existing test) behaves exactly as before: the map falls\n// back to its per-widget options, and to a plain panel when it has none. Adding the\n// provider is what turns a tenant's configured basemap on; nothing breaks without it.\n\nimport { createContext, useContext, type ReactNode } from 'react';\nimport type { Basemap } from '@devicechain/client';\n\nconst TenantBasemapContext = createContext<Basemap | null>(null);\n\n/**\n * Supplies the tenant's effective basemap to every map widget beneath it.\n *\n * Both hosts install this ONCE, high in the tree, rather than at each place they\n * render a widget: the console from its TenantProvider, the /dash viewer app from the\n * one place it mounts a DashboardRenderer.\n *\n * \uD83D\uDD34 That placement is the whole safety argument, because a host that forgets this\n * does NOT fail loudly \u2014 its maps simply render as they did before the tenant setting\n * existed, on that surface only. Wrapping individual render sites is the version that\n * silently half-works; there is no render site to forget from the top of the tree.\n */\nexport function TenantBasemapProvider({\n basemap,\n children,\n}: {\n basemap: Basemap | null;\n children: ReactNode;\n}) {\n return <TenantBasemapContext.Provider value={basemap}>{children}</TenantBasemapContext.Provider>;\n}\n\n/**\n * The tenant's effective basemap, or null when no host supplied one.\n *\n * Null means \"no tenant basemap in force\" \u2014 either the host installs no provider, or\n * neither the tenant nor the operator has configured one. Both cases resolve the same\n * way downstream (fall back to the per-widget override, then to a plain panel), so\n * callers do not need to distinguish them.\n */\nexport function useTenantBasemap(): Basemap | null {\n return useContext(TenantBasemapContext);\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// The map runtime: the pieces of MapLibre that only the HOST'S BUNDLER can produce.\n//\n// \uD83D\uDD34 WHY THIS EXISTS AT ALL. MapLibre parses tiles and geometry off the main thread, and\n// left to itself it derives the worker's URL at runtime from its own module URL \u2014\n// `new URL('./maplibre-gl-worker.mjs', import.meta.url)`. That is a string no bundler can\n// follow, so nothing emits the file, the browser resolves it to a 404, and `new Worker()`\n// does not throw on one: the failure arrives later as an async error event. The map builds,\n// type-checks, passes every test, and renders nothing.\n//\n// The only thing that can emit that file is the application's own bundler, using its own\n// dialect \u2014 Vite spells it `?worker&url`, webpack and Rollup each spell it differently.\n// So the URL is something a HOST computes and a LIBRARY cannot.\n//\n// \uD83D\uDD34 THIS PACKAGE USED TO WRITE VITE'S DIALECT ITSELF, and that is the defect this seam\n// removes. A published library containing `import('\u2026?worker&url')` works under a consumer's\n// Vite and silently renders a blank map under anything else \u2014 and because the specifier is\n// externalized, no bundler ever tries to resolve it, so NOTHING IN ANY BUILD REPORTS IT.\n// A green pipeline over a broken artifact is strictly worse than a red one.\n//\n// \u26A0\uFE0F HOW THIS DIFFERS FROM TenantBasemapProvider, which sits beside it and looks alike.\n// The basemap provider is OPTIONAL BY DESIGN: a host that installs none behaves exactly as\n// it did before the tenant setting existed, and that is a coherent state. This one is\n// REQUIRED, and a host that omits it gets a loud notice rather than a map. The asymmetry is\n// deliberate and is the whole point: a missing basemap is a configuration a viewer can read\n// off the screen, while a missing worker URL is invisible \u2014 it is the blank map above.\n// Copying the basemap's optional-by-default shape here would relocate that defect into\n// every unwired host instead of removing it.\n\nimport { createContext, useContext, type ReactNode } from 'react';\n\n/**\n * The bundler-produced runtime a map widget needs from its host.\n *\n * `workerUrl` is the emitted, host-resolvable URL of MapLibre's worker entry\n * (`maplibre-gl/dist/maplibre-gl-worker.mjs`). It is handed to MapLibre's `setWorkerUrl`\n * before the first `Map` is constructed.\n *\n * `loadStyles` is OPTIONAL, and imports MapLibre's stylesheet\n * (`maplibre-gl/dist/maplibre-gl.css`). Omit it and import the stylesheet yourself, the way\n * every map library asks you to \u2014 that is the ordinary path and nothing here second-guesses\n * it. Supplying it buys one thing: the CSS rides the renderer's lazy chunk instead of your\n * main stylesheet, which is worth **83 KB raw / 10.7 KB gzipped** to a viewer who never\n * opens a board with a map on it.\n *\n * \uD83D\uDD34 The size is measured against maplibre-gl 6.6.0, the version this package peers on. An\n * earlier draft said 70 KB \"measured, not estimated\" \u2014 measured, but against a stale\n * `node_modules` carrying 6.2.0. Measuring the right thing in the wrong tree reads exactly\n * like measuring, which is why the version is named here.\n *\n * \uD83D\uDD34 It is a FUNCTION rather than a boolean or a URL because the host's bundler owns its\n * CSS pipeline: only the host can say \"load this, lazily, your way\".\n */\nexport type MapRuntime = {\n workerUrl: string;\n loadStyles?: () => Promise<unknown>;\n};\n\nconst MapRuntimeContext = createContext<MapRuntime | null>(null);\n\n/**\n * Supplies the host's MapLibre runtime to every map widget beneath it.\n *\n * Install it ONCE, high in the tree, next to TenantBasemapProvider \u2014 the console from its\n * TenantProvider, the /dash viewer from the one place it mounts a DashboardRenderer.\n *\n * \uD83D\uDD11 ON VITE, DO NOT WRITE ANY OF THIS \u2014 import the ready-made runtime:\n *\n * ```tsx\n * import { viteMapRuntime } from '@devicechain/widgets/vite';\n *\n * <MapRuntimeProvider runtime={viteMapRuntime}>{children}</MapRuntimeProvider>\n * ```\n *\n * On any other bundler you supply the URL yourself, and there is ONE REQUIREMENT that\n * decides whether it works: MapLibre loads the URL as a MODULE worker, so whatever it\n * serves must be a module with no unresolved imports left in it.\n * `maplibre-gl/dist/maplibre-gl-worker.mjs` does not satisfy that on its own \u2014 its first\n * line is `import \u2026 from './maplibre-gl-shared.mjs'`.\n *\n * \uD83D\uDD34 SO DO NOT POINT AT A VERBATIM COPY OF THE WORKER FILE, and in particular do not\n * reach for `new URL('maplibre-gl/dist/maplibre-gl-worker.mjs', import.meta.url)`. That\n * looks exactly right and is wrong: webpack treats the target as an ASSET and copies it,\n * the sibling is never emitted, and the worker dies on its own first line. Measured\n * against webpack 5, from a packed tarball: the worker URL answered 200, both maps drew\n * canvases, all markers appeared, tiles loaded, THE BUILD EXITED 0 AND THE CONSOLE WAS\n * EMPTY \u2014 and the bundled basemap rendered ocean with no land on it.\n *\n * Two recipes that were measured working, in `hack/consumer-proof`:\n *\n * ```js\n * // webpack \u2014 make the worker a second ENTRY, so its imports are bundled into it.\n * entry: { main: './src/index.tsx', 'maplibre-worker': 'maplibre-gl/dist/maplibre-gl-worker.mjs' }\n * // \u2026then: <MapRuntimeProvider runtime={{ workerUrl: '/maplibre-worker.js' }}>\n * ```\n *\n * ```js\n * // any bundler, or none \u2014 copy the worker AND ITS SIBLING, under their own names,\n * // into one served directory: maplibre-gl-worker.mjs + maplibre-gl-shared.mjs\n * // \u2026then: <MapRuntimeProvider runtime={{ workerUrl: '/vendor/maplibre-gl-worker.mjs' }}>\n * ```\n *\n * \uD83D\uDD34 Hold the runtime in a MODULE CONSTANT, as above, rather than building it inline in\n * JSX. An object literal in the element is a new identity on every render, and while the\n * widget deliberately keys its effects on `workerUrl` rather than on the object to survive\n * that, a host that also passes it anywhere keyed on identity would tear down and rebuild\n * the map on every parent render. There is nothing per-render in it to justify the risk.\n *\n * \uD83D\uDD34 `maplibre-gl` is a PEER dependency of this package precisely so that the copy the host\n * takes the worker from is the same copy this package renders with. A worker built from one\n * version driving a main thread from another is the blank map again, by a different route.\n */\nexport function MapRuntimeProvider({\n runtime,\n children,\n}: {\n runtime: MapRuntime;\n children: ReactNode;\n}) {\n return <MapRuntimeContext.Provider value={runtime}>{children}</MapRuntimeContext.Provider>;\n}\n\n/**\n * The host's map runtime, or null when no host supplied one.\n *\n * \uD83D\uDD34 Callers MUST treat null as a refusal to render a map, not as a default to paper over.\n * There is no sensible fallback: the only alternative to a host-supplied URL is MapLibre's\n * own runtime derivation, which is the broken behaviour this seam exists to prevent.\n */\nexport function useMapRuntime(): MapRuntime | null {\n return useContext(MapRuntimeContext);\n}\n"],
|
|
5
|
+
"mappings": ";AAeA,SAAS,eAAe,kBAAkC;AAwBjD;AArBT,IAAM,uBAAuB,cAA8B,IAAI;AAcxD,SAAS,sBAAsB;AAAA,EACpC;AAAA,EACA;AACF,GAGG;AACD,SAAO,oBAAC,qBAAqB,UAArB,EAA8B,OAAO,SAAU,UAAS;AAClE;AAUO,SAAS,mBAAmC;AACjD,SAAO,WAAW,oBAAoB;AACxC;;;ACrBA,SAAS,iBAAAA,gBAAe,cAAAC,mBAAkC;AA0FjD,gBAAAC,YAAA;AA7DT,IAAM,oBAAoBF,eAAiC,IAAI;AAsDxD,SAAS,mBAAmB;AAAA,EACjC;AAAA,EACA;AACF,GAGG;AACD,SAAO,gBAAAE,KAAC,kBAAkB,UAAlB,EAA2B,OAAO,SAAU,UAAS;AAC/D;AASO,SAAS,gBAAmC;AACjD,SAAOD,YAAW,iBAAiB;AACrC;",
|
|
6
|
+
"names": ["createContext", "useContext", "jsx"]
|
|
7
|
+
}
|
|
@@ -1,20 +1,20 @@
|
|
|
1
|
+
import {
|
|
2
|
+
measurementSpan
|
|
3
|
+
} from "./chunk-GUCEPNGM.js";
|
|
1
4
|
import {
|
|
2
5
|
fleetStrings
|
|
3
6
|
} from "./chunk-QMGKB6J3.js";
|
|
7
|
+
import "./chunk-BRH66T5F.js";
|
|
4
8
|
import {
|
|
5
9
|
Explained
|
|
6
10
|
} from "./chunk-AF4M5OXQ.js";
|
|
7
11
|
import "./chunk-JVC6JYUR.js";
|
|
8
|
-
import {
|
|
9
|
-
samplesExport
|
|
10
|
-
} from "./chunk-VTGKDO44.js";
|
|
11
|
-
import {
|
|
12
|
-
measurementSpan
|
|
13
|
-
} from "./chunk-NYHNQ5VR.js";
|
|
14
|
-
import "./chunk-BRH66T5F.js";
|
|
15
12
|
import {
|
|
16
13
|
LevelEmpty
|
|
17
14
|
} from "./chunk-EQOWBDS7.js";
|
|
15
|
+
import {
|
|
16
|
+
samplesExport
|
|
17
|
+
} from "./chunk-VTGKDO44.js";
|
|
18
18
|
import "./chunk-XJ7UF2CP.js";
|
|
19
19
|
import {
|
|
20
20
|
seedWindow
|
|
@@ -368,4 +368,4 @@ export {
|
|
|
368
368
|
FLUSH_MS,
|
|
369
369
|
CompareFeed as default
|
|
370
370
|
};
|
|
371
|
-
//# sourceMappingURL=compare-feed-
|
|
371
|
+
//# sourceMappingURL=compare-feed-MLHG7YFN.js.map
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
} from "./chunk-XNEBAY4J.js";
|
|
4
4
|
import {
|
|
5
5
|
measurementSpan
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-GUCEPNGM.js";
|
|
7
7
|
|
|
8
8
|
// src/event-markers-feed.tsx
|
|
9
9
|
import {
|
|
@@ -370,4 +370,4 @@ function EventList({
|
|
|
370
370
|
export {
|
|
371
371
|
EventMarkersFeed as default
|
|
372
372
|
};
|
|
373
|
-
//# sourceMappingURL=event-markers-feed-
|
|
373
|
+
//# sourceMappingURL=event-markers-feed-DMRW5GV5.js.map
|
|
@@ -2,12 +2,12 @@ import {
|
|
|
2
2
|
ChunkLoadingFrame,
|
|
3
3
|
lazyChunk
|
|
4
4
|
} from "./chunk-BMFQOTYG.js";
|
|
5
|
+
import {
|
|
6
|
+
measurementSpan
|
|
7
|
+
} from "./chunk-GUCEPNGM.js";
|
|
5
8
|
import {
|
|
6
9
|
fleetStrings
|
|
7
10
|
} from "./chunk-QMGKB6J3.js";
|
|
8
|
-
import {
|
|
9
|
-
measurementSpan
|
|
10
|
-
} from "./chunk-NYHNQ5VR.js";
|
|
11
11
|
import "./chunk-BRH66T5F.js";
|
|
12
12
|
import {
|
|
13
13
|
LevelEmpty
|
|
@@ -143,7 +143,7 @@ function shownMeasurements(widget) {
|
|
|
143
143
|
function comparesMembers(descriptor) {
|
|
144
144
|
return descriptor.options.maxSeries !== void 0;
|
|
145
145
|
}
|
|
146
|
-
var CompareFeed = lazyChunk(() => import("./compare-feed-
|
|
146
|
+
var CompareFeed = lazyChunk(() => import("./compare-feed-MLHG7YFN.js")).component("default", void 0, ChunkLoadingFrame);
|
|
147
147
|
function FleetEntry(props) {
|
|
148
148
|
if (props.refuse) return /* @__PURE__ */ jsx(GroupRefusal, { widget: props.widget, descriptor: props.descriptor });
|
|
149
149
|
if (comparesMembers(props.descriptor)) {
|
|
@@ -308,4 +308,4 @@ export {
|
|
|
308
308
|
FleetEntry as default,
|
|
309
309
|
shownMeasurements
|
|
310
310
|
};
|
|
311
|
-
//# sourceMappingURL=fleet-feed-
|
|
311
|
+
//# sourceMappingURL=fleet-feed-AKJ7QIMP.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
measurementSpan
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-GUCEPNGM.js";
|
|
4
4
|
import {
|
|
5
5
|
ACCESS_STRINGS
|
|
6
6
|
} from "./chunk-BRH66T5F.js";
|
|
@@ -335,4 +335,4 @@ export {
|
|
|
335
335
|
HistoryFeed,
|
|
336
336
|
HistoryTable
|
|
337
337
|
};
|
|
338
|
-
//# sourceMappingURL=history-widgets-
|
|
338
|
+
//# sourceMappingURL=history-widgets-DZBMNMEX.js.map
|
|
@@ -7,11 +7,11 @@ import {
|
|
|
7
7
|
import {
|
|
8
8
|
fleetStrings
|
|
9
9
|
} from "./chunk-QMGKB6J3.js";
|
|
10
|
+
import "./chunk-BRH66T5F.js";
|
|
10
11
|
import {
|
|
11
12
|
Explained
|
|
12
13
|
} from "./chunk-AF4M5OXQ.js";
|
|
13
14
|
import "./chunk-JVC6JYUR.js";
|
|
14
|
-
import "./chunk-BRH66T5F.js";
|
|
15
15
|
import {
|
|
16
16
|
LevelEmpty,
|
|
17
17
|
LevelReadout
|
|
@@ -351,4 +351,4 @@ export {
|
|
|
351
351
|
OnlineDoughnut,
|
|
352
352
|
PresenceFeed
|
|
353
353
|
};
|
|
354
|
-
//# sourceMappingURL=presence-widgets-
|
|
354
|
+
//# sourceMappingURL=presence-widgets-7OGANOLB.js.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import {
|
|
2
|
+
MapRuntimeProvider,
|
|
3
|
+
TenantBasemapProvider,
|
|
4
|
+
useMapRuntime,
|
|
5
|
+
useTenantBasemap
|
|
6
|
+
} from "./chunks/chunk-QHTO5ZEM.js";
|
|
7
|
+
export {
|
|
8
|
+
MapRuntimeProvider,
|
|
9
|
+
TenantBasemapProvider,
|
|
10
|
+
useMapRuntime,
|
|
11
|
+
useTenantBasemap
|
|
12
|
+
};
|
|
13
|
+
//# sourceMappingURL=host-context.js.map
|
package/dist/index.d.ts
CHANGED
|
@@ -28,5 +28,5 @@ export { placeable, projectToPanel, describePosition, formatDegrees, rasterStyle
|
|
|
28
28
|
export { severityColor, severityLabel, severityRank, highestSeverity, } from './widgets/severity';
|
|
29
29
|
export { commandStatusColor, commandStatusLabel, isTerminalStatus } from './widgets/command-status';
|
|
30
30
|
export { parseParameterSchema, defaultValues, validateParams, buildPayload, isScalar, } from './widgets/command-params';
|
|
31
|
-
export { WidgetFrame, WidgetSubjectProvider, WidgetSelectProvider, useWidgetSelect, WidgetTimeZoneProvider, useWidgetTimeZone, WidgetThemeRootProvider, useWidgetThemeRoot, WidgetCandidatesProvider, useWidgetCandidates, useFlashOnChange, flashTextStyle, flashBackgroundStyle, FlashValue, fitValueFont, useMeasurementStream, useAlarmStream, useCommandStream, useLocationStream, useDatasourceAvailability, useDashboardHub, useHubConnectionState, HubConnectionBadge, useResolvedBindings, useSlotCandidates, useCandidates, css, type FlashDirection, type WidgetFrameProps, type WidgetSelect, type WidgetCandidates, type MeasurementStreamState, type AlarmStreamState, type CommandStreamState, type LocationStreamState, type CandidatesState, type DatasourceAvailability, type HubConnectionBadgeProps, type ThemeVar, } from '@devicechain/widget-sdk';
|
|
31
|
+
export { WidgetFrame, WidgetSubjectProvider, WidgetSelectProvider, useWidgetSelect, WidgetTimeZoneProvider, useWidgetTimeZone, WidgetThemeRootProvider, useWidgetThemeRoot, WidgetCandidatesProvider, useWidgetCandidates, useFlashOnChange, flashTextStyle, flashBackgroundStyle, FlashValue, fitValueFont, useMeasurementStream, useAlarmStream, useCommandStream, useLocationStream, useDatasourceAvailability, useDashboardHub, useHubConnectionState, HubConnectionBadge, useResolvedBindings, useViewerBindings, useSlotCandidates, useCandidates, css, type FlashDirection, type WidgetFrameProps, type WidgetSelect, type WidgetCandidates, type MeasurementStreamState, type AlarmStreamState, type CommandStreamState, type LocationStreamState, type CandidatesState, type DatasourceAvailability, type HubConnectionBadgeProps, type ThemeVar, } from '@devicechain/widget-sdk';
|
|
32
32
|
export { EChart, buildLineOption, buildGaugeOption, resolveChartTheme, useChartTheme, type EChartOption, type LineChartOptions, type GaugeChartOptions, type ChartTheme, } from '@devicechain/widget-sdk/echarts';
|