@sjvair/map-sdk 0.0.0-stage → 0.1.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +506 -2
- package/dist/core/basemap.svelte.d.ts +38 -0
- package/dist/core/basemap.svelte.js +163 -0
- package/dist/core/context.d.ts +5 -0
- package/dist/core/context.js +13 -0
- package/dist/core/create-map.svelte.d.ts +207 -0
- package/dist/core/create-map.svelte.js +376 -0
- package/dist/core/gl.d.ts +92 -0
- package/dist/core/gl.js +1 -0
- package/dist/core/interactions.svelte.d.ts +43 -0
- package/dist/core/interactions.svelte.js +230 -0
- package/dist/core/plugin-host.svelte.d.ts +46 -0
- package/dist/core/plugin-host.svelte.js +366 -0
- package/dist/core/plugin.d.ts +155 -0
- package/dist/core/plugin.js +1 -0
- package/dist/core/scoped-context.d.ts +7 -0
- package/dist/core/scoped-context.js +34 -0
- package/dist/data/format.d.ts +10 -0
- package/dist/data/format.js +24 -0
- package/dist/data/index.d.ts +9 -0
- package/dist/data/index.js +9 -0
- package/dist/data/messages/en.json +52 -0
- package/dist/data/messages.d.ts +10 -0
- package/dist/data/messages.js +17 -0
- package/dist/data/metadata.d.ts +22 -0
- package/dist/data/metadata.js +110 -0
- package/dist/data/refs.d.ts +18 -0
- package/dist/data/refs.js +61 -0
- package/dist/data/scale-types.d.ts +7 -0
- package/dist/data/scale-types.js +1 -0
- package/dist/data/scale.d.ts +13 -0
- package/dist/data/scale.js +27 -0
- package/dist/data/selection.d.ts +7 -0
- package/dist/data/selection.js +31 -0
- package/dist/data/static-source.d.ts +4 -0
- package/dist/data/static-source.js +3 -0
- package/dist/data/types.d.ts +69 -0
- package/dist/data/types.js +1 -0
- package/dist/index.d.ts +56 -0
- package/dist/index.js +41 -0
- package/dist/plugins/icons.d.ts +21 -0
- package/dist/plugins/icons.js +73 -0
- package/dist/plugins/points-clusters.d.ts +60 -0
- package/dist/plugins/points-clusters.js +93 -0
- package/dist/plugins/points.svelte.d.ts +100 -0
- package/dist/plugins/points.svelte.js +501 -0
- package/dist/plugins/scale-resolution.svelte.d.ts +27 -0
- package/dist/plugins/scale-resolution.svelte.js +111 -0
- package/dist/plugins/shapes.svelte.d.ts +29 -0
- package/dist/plugins/shapes.svelte.js +307 -0
- package/dist/plugins/shared.d.ts +6 -0
- package/dist/plugins/shared.js +18 -0
- package/dist/plugins/style.d.ts +57 -0
- package/dist/plugins/style.js +193 -0
- package/dist/presets/CollocationTooltip.svelte +132 -0
- package/dist/presets/CollocationTooltip.svelte.d.ts +16 -0
- package/dist/presets/MonitorClusterTooltip.svelte +149 -0
- package/dist/presets/MonitorClusterTooltip.svelte.d.ts +16 -0
- package/dist/presets/MonitorReadingBox.svelte +58 -0
- package/dist/presets/MonitorReadingBox.svelte.d.ts +12 -0
- package/dist/presets/MonitorTooltip.svelte +88 -0
- package/dist/presets/MonitorTooltip.svelte.d.ts +20 -0
- package/dist/presets/calheatscore.d.ts +24 -0
- package/dist/presets/calheatscore.js +22 -0
- package/dist/presets/collocation-sites.d.ts +18 -0
- package/dist/presets/collocation-sites.js +67 -0
- package/dist/presets/fire-groups.d.ts +26 -0
- package/dist/presets/fire-groups.js +51 -0
- package/dist/presets/forecast-zones.d.ts +25 -0
- package/dist/presets/forecast-zones.js +23 -0
- package/dist/presets/hms-fire.d.ts +26 -0
- package/dist/presets/hms-fire.js +87 -0
- package/dist/presets/hms-smoke.d.ts +19 -0
- package/dist/presets/hms-smoke.js +20 -0
- package/dist/presets/monitors.d.ts +48 -0
- package/dist/presets/monitors.js +243 -0
- package/dist/presets/reactive-props.svelte.d.ts +4 -0
- package/dist/presets/reactive-props.svelte.js +7 -0
- package/dist/presets/regions.d.ts +24 -0
- package/dist/presets/regions.js +22 -0
- package/dist/stores/abort.d.ts +16 -0
- package/dist/stores/abort.js +52 -0
- package/dist/stores/collocation-store.svelte.d.ts +25 -0
- package/dist/stores/collocation-store.svelte.js +72 -0
- package/dist/stores/hms-stores.svelte.d.ts +35 -0
- package/dist/stores/hms-stores.svelte.js +97 -0
- package/dist/stores/index.d.ts +4 -0
- package/dist/stores/index.js +4 -0
- package/dist/stores/load-once.svelte.d.ts +25 -0
- package/dist/stores/load-once.svelte.js +93 -0
- package/dist/stores/monitors-store.svelte.d.ts +85 -0
- package/dist/stores/monitors-store.svelte.js +340 -0
- package/dist/theme.css +34 -0
- package/dist/ui/FeatureList.svelte +505 -0
- package/dist/ui/FeatureList.svelte.d.ts +14 -0
- package/dist/ui/Legend.svelte +213 -0
- package/dist/ui/Legend.svelte.d.ts +22 -0
- package/dist/ui/LegendBar.svelte +161 -0
- package/dist/ui/LegendBar.svelte.d.ts +11 -0
- package/dist/ui/MapControls.svelte +135 -0
- package/dist/ui/MapControls.svelte.d.ts +11 -0
- package/dist/ui/MapTooltip.svelte +158 -0
- package/dist/ui/MapTooltip.svelte.d.ts +28 -0
- package/dist/ui/SelectionBar.svelte +123 -0
- package/dist/ui/SelectionBar.svelte.d.ts +10 -0
- package/dist/ui/TooltipItem.svelte +72 -0
- package/dist/ui/TooltipItem.svelte.d.ts +11 -0
- package/dist/ui/ViewSummary.svelte +97 -0
- package/dist/ui/ViewSummary.svelte.d.ts +7 -0
- package/dist/ui/controls.css +45 -0
- package/dist/ui/data-credits.svelte.d.ts +9 -0
- package/dist/ui/data-credits.svelte.js +52 -0
- package/dist/ui/feature-list.d.ts +38 -0
- package/dist/ui/feature-list.js +95 -0
- package/dist/ui/legend-data.d.ts +41 -0
- package/dist/ui/legend-data.js +40 -0
- package/dist/ui/level-chip.d.ts +11 -0
- package/dist/ui/level-chip.js +15 -0
- package/dist/ui/value-format.d.ts +7 -0
- package/dist/ui/value-format.js +13 -0
- package/dist/ui/view-summary.d.ts +11 -0
- package/dist/ui/view-summary.js +33 -0
- package/dist/view/MapView.svelte +373 -0
- package/dist/view/MapView.svelte.d.ts +43 -0
- package/dist/view/create-gl-map.d.ts +34 -0
- package/dist/view/create-gl-map.js +182 -0
- package/dist/view/list-placement.d.ts +21 -0
- package/dist/view/list-placement.js +13 -0
- package/package.json +103 -5
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
import { getMonitors, getMonitorsList, getMonitorsMeta } from "@sjvair/sdk";
|
|
2
|
+
import { encodeRef } from "../data/refs.js";
|
|
3
|
+
import { anySignal, untilAborted } from "./abort.js";
|
|
4
|
+
const DEFAULT_POLL_INTERVAL_MS = 120_000;
|
|
5
|
+
const AIR_QUALITY = "air-quality";
|
|
6
|
+
/** Every monitor with its latest reading for `entryType`. */
|
|
7
|
+
async function getReadings(entryType, signal) {
|
|
8
|
+
// The SDK types entry types as a closed union; the server decides which exist.
|
|
9
|
+
const monitors = await getMonitors(entryType, undefined, { signal });
|
|
10
|
+
return monitors;
|
|
11
|
+
}
|
|
12
|
+
/** The reading's numeric value, or null. */
|
|
13
|
+
function readingValue(monitor) {
|
|
14
|
+
const raw = "value" in monitor.latest ? monitor.latest.value : null;
|
|
15
|
+
if (raw === null || raw === "")
|
|
16
|
+
return null;
|
|
17
|
+
const value = Number(raw);
|
|
18
|
+
return Number.isFinite(value) ? value : null;
|
|
19
|
+
}
|
|
20
|
+
function toCollection(readings) {
|
|
21
|
+
const features = [];
|
|
22
|
+
for (const monitor of readings) {
|
|
23
|
+
if (!monitor.position)
|
|
24
|
+
continue;
|
|
25
|
+
features.push({
|
|
26
|
+
type: "Feature",
|
|
27
|
+
geometry: { type: "Point", coordinates: monitor.position.coordinates },
|
|
28
|
+
properties: {
|
|
29
|
+
refKey: encodeRef({ kind: "monitor", id: monitor.id }),
|
|
30
|
+
name: monitor.name,
|
|
31
|
+
value: readingValue(monitor),
|
|
32
|
+
type: monitor.type,
|
|
33
|
+
isActive: monitor.is_active,
|
|
34
|
+
isSjvair: monitor.is_sjvair,
|
|
35
|
+
location: monitor.location === "inside" ? "inside" : "outside",
|
|
36
|
+
timestamp: monitor.latest.timestamp
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
return { type: "FeatureCollection", features };
|
|
41
|
+
}
|
|
42
|
+
class LiveMonitorsStore {
|
|
43
|
+
#meta = $state.raw(null);
|
|
44
|
+
#status = $state.raw({ state: "loading" });
|
|
45
|
+
initialized = $state(false);
|
|
46
|
+
entryType = $state(null);
|
|
47
|
+
monitors = $state.raw(new Map());
|
|
48
|
+
availableEntryTypes = $derived(this.#meta?.available_pollutants ?? []);
|
|
49
|
+
get meta() {
|
|
50
|
+
return this.#meta;
|
|
51
|
+
}
|
|
52
|
+
latest = $state.raw(new Map());
|
|
53
|
+
dataSource;
|
|
54
|
+
/** Null before init; built when the readings arrive, so every reader shares one object. */
|
|
55
|
+
#features = $state.raw(null);
|
|
56
|
+
/** True before init and while an entry type change refetches (the markers blank). */
|
|
57
|
+
#blank = true;
|
|
58
|
+
#scale = $derived(this.entryType === null ? null : { dataset: AIR_QUALITY, entryType: this.entryType });
|
|
59
|
+
#pollIntervalMs;
|
|
60
|
+
#pollTimer = null;
|
|
61
|
+
#initRun = null;
|
|
62
|
+
#requestedEntryType = null;
|
|
63
|
+
#requestedAutoUpdate = false;
|
|
64
|
+
#updateController = null;
|
|
65
|
+
#disposed = false;
|
|
66
|
+
constructor(options) {
|
|
67
|
+
this.#pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
68
|
+
// eslint-disable-next-line @typescript-eslint/no-this-alias -- getters below read the store
|
|
69
|
+
const store = this;
|
|
70
|
+
this.dataSource = {
|
|
71
|
+
get status() {
|
|
72
|
+
return store.#status;
|
|
73
|
+
},
|
|
74
|
+
get features() {
|
|
75
|
+
return store.#features;
|
|
76
|
+
},
|
|
77
|
+
get scale() {
|
|
78
|
+
return store.#scale;
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
entryTypeFor(next) {
|
|
83
|
+
const meta = this.#meta;
|
|
84
|
+
if (this.initialized && meta && !meta.available_pollutants.includes(next)) {
|
|
85
|
+
next = meta.default_pollutant;
|
|
86
|
+
}
|
|
87
|
+
if (next === this.entryType)
|
|
88
|
+
return;
|
|
89
|
+
this.entryType = next;
|
|
90
|
+
if (!this.initialized || this.#disposed)
|
|
91
|
+
return;
|
|
92
|
+
this.#setReadings([]);
|
|
93
|
+
this.#blank = true;
|
|
94
|
+
this.#status = { state: "loading" };
|
|
95
|
+
this.#refresh();
|
|
96
|
+
}
|
|
97
|
+
init(entryType, options = {}) {
|
|
98
|
+
if (this.initialized || this.#disposed)
|
|
99
|
+
return Promise.resolve();
|
|
100
|
+
const { signal } = options;
|
|
101
|
+
// Nothing is recorded or started for a call that is already cancelled.
|
|
102
|
+
if (signal?.aborted)
|
|
103
|
+
return Promise.reject(signal.reason);
|
|
104
|
+
if (options.autoUpdate !== false)
|
|
105
|
+
this.#requestedAutoUpdate = true;
|
|
106
|
+
if (entryType)
|
|
107
|
+
this.#requestedEntryType = entryType;
|
|
108
|
+
const run = (this.#initRun ??= this.#startInitRun());
|
|
109
|
+
if (!signal) {
|
|
110
|
+
run.unsignalled = true;
|
|
111
|
+
return run.promise;
|
|
112
|
+
}
|
|
113
|
+
run.signalled++;
|
|
114
|
+
const onAbort = () => {
|
|
115
|
+
run.aborted++;
|
|
116
|
+
if (!run.unsignalled && run.aborted === run.signalled)
|
|
117
|
+
this.#abortInitRun(run);
|
|
118
|
+
};
|
|
119
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
120
|
+
const cleanup = () => signal.removeEventListener("abort", onAbort);
|
|
121
|
+
run.promise.then(cleanup, cleanup);
|
|
122
|
+
return untilAborted(run.promise, signal);
|
|
123
|
+
}
|
|
124
|
+
#startInitRun() {
|
|
125
|
+
const controller = new AbortController();
|
|
126
|
+
if (this.#status.state !== "loading")
|
|
127
|
+
this.#status = { state: "loading" };
|
|
128
|
+
const run = {
|
|
129
|
+
// Cancelling rejects at once, even while a request that ignores the signal is pending.
|
|
130
|
+
promise: untilAborted(this.#runInit(controller.signal), controller.signal).then(() => {
|
|
131
|
+
if (this.#initRun === run)
|
|
132
|
+
this.#clearInitRun();
|
|
133
|
+
}, (error) => {
|
|
134
|
+
// Recorded before clearing, so retry() repeats the failed call.
|
|
135
|
+
const entryType = this.#requestedEntryType ?? undefined;
|
|
136
|
+
const autoUpdate = this.#requestedAutoUpdate;
|
|
137
|
+
if (this.#initRun === run)
|
|
138
|
+
this.#clearInitRun();
|
|
139
|
+
if (!controller.signal.aborted) {
|
|
140
|
+
this.#status = {
|
|
141
|
+
state: "error",
|
|
142
|
+
error,
|
|
143
|
+
retry: () => {
|
|
144
|
+
this.init(entryType, { autoUpdate }).catch(() => { });
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
throw error;
|
|
149
|
+
}),
|
|
150
|
+
controller,
|
|
151
|
+
signalled: 0,
|
|
152
|
+
aborted: 0,
|
|
153
|
+
unsignalled: false
|
|
154
|
+
};
|
|
155
|
+
return run;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Cancels a run nobody is waiting on. It is detached at once, so a call arriving before its
|
|
159
|
+
* rejection settles starts a fresh run instead of joining the cancelled one.
|
|
160
|
+
*/
|
|
161
|
+
#abortInitRun(run) {
|
|
162
|
+
if (this.#initRun !== run)
|
|
163
|
+
return;
|
|
164
|
+
this.#clearInitRun();
|
|
165
|
+
run.controller.abort();
|
|
166
|
+
}
|
|
167
|
+
#clearInitRun() {
|
|
168
|
+
this.#initRun = null;
|
|
169
|
+
this.#requestedEntryType = null;
|
|
170
|
+
this.#requestedAutoUpdate = false;
|
|
171
|
+
}
|
|
172
|
+
/** The requested entry type when the server lists it, else the server's default. */
|
|
173
|
+
#wantedEntryType(meta) {
|
|
174
|
+
const requested = this.#requestedEntryType;
|
|
175
|
+
return requested && meta.available_pollutants.includes(requested)
|
|
176
|
+
? requested
|
|
177
|
+
: meta.default_pollutant;
|
|
178
|
+
}
|
|
179
|
+
async #runInit(signal) {
|
|
180
|
+
// The entry type the latest readings were requested for.
|
|
181
|
+
let fetchedEntryType = this.#requestedEntryType;
|
|
182
|
+
const metaRequest = getMonitorsMeta({ signal });
|
|
183
|
+
const listAndMeta = Promise.all([getMonitorsList({ signal }), metaRequest]).then(([list, meta]) => {
|
|
184
|
+
if (signal.aborted)
|
|
185
|
+
throw signal.reason;
|
|
186
|
+
this.monitors = new Map(list.map((monitor) => [monitor.id, monitor]));
|
|
187
|
+
this.#meta = meta;
|
|
188
|
+
return meta;
|
|
189
|
+
});
|
|
190
|
+
// The latest readings only need the entry type, so they never wait for the list. An
|
|
191
|
+
// explicit entry type may still arrive before meta does, in which case the default is never
|
|
192
|
+
// fetched. An explicit one is requested before meta can say whether the server lists it; if
|
|
193
|
+
// it doesn't, its failure is dropped and the default is fetched below.
|
|
194
|
+
const latestRequest = fetchedEntryType
|
|
195
|
+
? getReadings(fetchedEntryType, signal).catch(async (error) => {
|
|
196
|
+
const meta = await metaRequest;
|
|
197
|
+
if (this.#wantedEntryType(meta) === fetchedEntryType || signal.aborted)
|
|
198
|
+
throw error;
|
|
199
|
+
// Nothing was fetched: if this entry type is wanted again, it is fetched again.
|
|
200
|
+
fetchedEntryType = null;
|
|
201
|
+
return null;
|
|
202
|
+
})
|
|
203
|
+
: metaRequest.then((meta) => {
|
|
204
|
+
const entryType = this.#wantedEntryType(meta);
|
|
205
|
+
fetchedEntryType = entryType;
|
|
206
|
+
return getReadings(entryType, signal);
|
|
207
|
+
});
|
|
208
|
+
// One Promise.all so a failure in either never leaves the other unhandled.
|
|
209
|
+
const [meta, firstReadings] = await Promise.all([listAndMeta, latestRequest]);
|
|
210
|
+
let readings = firstReadings;
|
|
211
|
+
// An explicit entry type may have arrived while the requests above were in flight.
|
|
212
|
+
let wanted = this.#wantedEntryType(meta);
|
|
213
|
+
while (wanted !== fetchedEntryType) {
|
|
214
|
+
readings = await getReadings(wanted, signal);
|
|
215
|
+
fetchedEntryType = wanted;
|
|
216
|
+
wanted = this.#wantedEntryType(meta);
|
|
217
|
+
}
|
|
218
|
+
// Cancelled while a request that ignores the signal was settling: leave state to the next run.
|
|
219
|
+
if (signal.aborted)
|
|
220
|
+
throw signal.reason;
|
|
221
|
+
this.entryType = fetchedEntryType;
|
|
222
|
+
// Non-null: the loop above leaves the readings of the entry type it settled on.
|
|
223
|
+
this.#setReadings(readings);
|
|
224
|
+
this.#status = { state: "ready" };
|
|
225
|
+
if (this.#requestedAutoUpdate)
|
|
226
|
+
this.#startPolling();
|
|
227
|
+
this.initialized = true;
|
|
228
|
+
}
|
|
229
|
+
#setReadings(readings) {
|
|
230
|
+
this.latest = new Map(readings.map((monitor) => [
|
|
231
|
+
monitor.id,
|
|
232
|
+
{ value: readingValue(monitor), timestamp: monitor.latest.timestamp }
|
|
233
|
+
]));
|
|
234
|
+
this.#features = toCollection(readings);
|
|
235
|
+
this.#blank = false;
|
|
236
|
+
}
|
|
237
|
+
#startPolling() {
|
|
238
|
+
if (this.#pollTimer !== null)
|
|
239
|
+
return;
|
|
240
|
+
this.#pollTimer = setInterval(() => this.#refresh(), this.#pollIntervalMs);
|
|
241
|
+
}
|
|
242
|
+
pauseAutoUpdate() {
|
|
243
|
+
if (this.#pollTimer === null)
|
|
244
|
+
return;
|
|
245
|
+
clearInterval(this.#pollTimer);
|
|
246
|
+
this.#pollTimer = null;
|
|
247
|
+
}
|
|
248
|
+
resumeAutoUpdate() {
|
|
249
|
+
if (!this.initialized || this.#disposed || this.#pollTimer !== null)
|
|
250
|
+
return;
|
|
251
|
+
this.#refresh();
|
|
252
|
+
this.#startPolling();
|
|
253
|
+
}
|
|
254
|
+
/** An update nobody awaits: polling, resuming, an entry type change, or a retry. */
|
|
255
|
+
#refresh() {
|
|
256
|
+
this.update().catch(console.error);
|
|
257
|
+
}
|
|
258
|
+
async update(options = {}) {
|
|
259
|
+
// Always set once initialized; the null check only narrows the type.
|
|
260
|
+
const entryType = this.entryType;
|
|
261
|
+
if (!this.initialized || this.#disposed || entryType === null)
|
|
262
|
+
return;
|
|
263
|
+
// Nothing has started yet, so an in-flight update is left alone.
|
|
264
|
+
if (options.signal?.aborted)
|
|
265
|
+
throw options.signal.reason;
|
|
266
|
+
const supersededInFlight = this.#updateController !== null;
|
|
267
|
+
this.#updateController?.abort();
|
|
268
|
+
const controller = new AbortController();
|
|
269
|
+
this.#updateController = controller;
|
|
270
|
+
const combined = anySignal(controller.signal, options.signal);
|
|
271
|
+
// The caller cancelled this update. If it is still the latest and it cut off an update that
|
|
272
|
+
// was in flight, that update's data is lost too, so refresh again without a signal.
|
|
273
|
+
const callerAborted = (signal) => {
|
|
274
|
+
if (supersededInFlight && this.#updateController === controller)
|
|
275
|
+
this.#refresh();
|
|
276
|
+
return signal.reason;
|
|
277
|
+
};
|
|
278
|
+
// Set once callerAborted has run, so the catch below does not run it a second time.
|
|
279
|
+
let abortedError;
|
|
280
|
+
try {
|
|
281
|
+
const [list, readings] = await Promise.all([
|
|
282
|
+
getMonitorsList({ signal: combined.signal }),
|
|
283
|
+
getReadings(entryType, combined.signal)
|
|
284
|
+
]);
|
|
285
|
+
// Superseded or cancelled while the requests were settling.
|
|
286
|
+
if (options.signal?.aborted)
|
|
287
|
+
throw (abortedError = callerAborted(options.signal));
|
|
288
|
+
if (controller.signal.aborted)
|
|
289
|
+
return;
|
|
290
|
+
// The entry type changed while this was in flight: these readings belong to the previous
|
|
291
|
+
// one, and the change has already started its own update.
|
|
292
|
+
if (this.entryType !== entryType)
|
|
293
|
+
return;
|
|
294
|
+
this.monitors = new Map(list.map((monitor) => [monitor.id, monitor]));
|
|
295
|
+
this.#setReadings(readings);
|
|
296
|
+
this.#status = { state: "ready" };
|
|
297
|
+
}
|
|
298
|
+
catch (error) {
|
|
299
|
+
// The caller's own abort rejects with its reason. A superseded update resolves quietly.
|
|
300
|
+
if (options.signal?.aborted)
|
|
301
|
+
throw error === abortedError ? error : callerAborted(options.signal);
|
|
302
|
+
if (controller.signal.aborted)
|
|
303
|
+
return;
|
|
304
|
+
// With readings still shown, a failed refresh keeps them (polling tries again); with
|
|
305
|
+
// none (after an entry type change), the source reports the failure.
|
|
306
|
+
if (this.#blank) {
|
|
307
|
+
this.#status = {
|
|
308
|
+
state: "error",
|
|
309
|
+
error,
|
|
310
|
+
retry: () => {
|
|
311
|
+
this.#status = { state: "loading" };
|
|
312
|
+
this.#refresh();
|
|
313
|
+
}
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
throw error;
|
|
317
|
+
}
|
|
318
|
+
finally {
|
|
319
|
+
combined.dispose();
|
|
320
|
+
if (this.#updateController === controller)
|
|
321
|
+
this.#updateController = null;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
dispose() {
|
|
325
|
+
this.#disposed = true;
|
|
326
|
+
this.pauseAutoUpdate();
|
|
327
|
+
this.#updateController?.abort();
|
|
328
|
+
if (this.#initRun)
|
|
329
|
+
this.#abortInitRun(this.#initRun);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* The live monitors data for an app: the monitor list, the latest readings for one entry type,
|
|
334
|
+
* and polling every 2 minutes. One store can feed any number of maps and screens, or none: its
|
|
335
|
+
* `dataSource` goes straight to `monitorsPlugin`. Call `init()` to load, and `dispose()` when the
|
|
336
|
+
* app is done with it.
|
|
337
|
+
*/
|
|
338
|
+
export function createMonitorsStore(options = {}) {
|
|
339
|
+
return new LiveMonitorsStore(options);
|
|
340
|
+
}
|
package/dist/theme.css
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* map-sdk's theme: import after Tailwind, e.g. `@import "@sjvair/map-sdk/theme.css";`.
|
|
3
|
+
*
|
|
4
|
+
* The components use the host's shadcn tokens (--background, --card, --primary, --border, …)
|
|
5
|
+
* and these family tokens. A host that defines a family token gets it; one that doesn't falls
|
|
6
|
+
* back to a standard shadcn token. Inline, so each utility reads the token where it is used and
|
|
7
|
+
* follows the host's dark mode.
|
|
8
|
+
*/
|
|
9
|
+
@theme inline {
|
|
10
|
+
--color-highlight: var(--highlight, var(--primary));
|
|
11
|
+
--color-highlight-subtle: var(--highlight-subtle, var(--muted));
|
|
12
|
+
--color-highlight-foreground: var(--highlight-foreground, var(--primary-foreground));
|
|
13
|
+
--color-card-border: var(--card-border, var(--border));
|
|
14
|
+
--color-overlay: var(--overlay, var(--background));
|
|
15
|
+
--color-link: var(--link, var(--primary));
|
|
16
|
+
--color-ring-edge: var(--ring-edge, var(--background));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/* Utilities, not theme keys: a theme key named like the host token would emit a self-referencing
|
|
20
|
+
`--shadow-card: var(--shadow-card, …)` in the theme layer, overriding the host's value. */
|
|
21
|
+
@utility shadow-card {
|
|
22
|
+
box-shadow: var(--shadow-card, none);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
@utility rounded-control {
|
|
26
|
+
border-radius: var(--radius-control, var(--radius));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
@utility shadow-lifted {
|
|
30
|
+
box-shadow: var(--shadow-lifted, var(--shadow-card, none));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/* The motion, swatch-outline, and --ring-edge tokens are read with fallbacks where used (see
|
|
34
|
+
ui/controls.css and the components); they are never declared here. */
|