@sjvair/map-sdk 0.1.0-beta.5 → 0.1.0-beta.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -3
- package/dist/core/create-map.svelte.d.ts +8 -0
- package/dist/core/create-map.svelte.js +3 -1
- package/dist/core/plugin-host.svelte.d.ts +8 -4
- package/dist/core/plugin-host.svelte.js +16 -5
- package/dist/data/messages/en.json +1 -0
- package/dist/ui/FeatureList.svelte +5 -10
- package/dist/ui/Legend.svelte +2 -1
- package/dist/ui/TooltipItem.svelte +3 -9
- package/dist/ui/feature-list.d.ts +5 -0
- package/dist/ui/feature-list.js +20 -4
- package/dist/view/MapView.svelte +1 -0
- package/dist/view/create-gl-map.js +4 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -135,7 +135,13 @@ default MapTiler Streets v4 variants (build on `defaultBasemaps()`, which `creat
|
|
|
135
135
|
sjvair.com setup is `{ ...defaultBasemaps(), excludedLayers: [] }`); `config.initialView` and
|
|
136
136
|
`config.cooperativeGestures` set the camera and gesture handling. `config.projection` (`"mercator"`
|
|
137
137
|
by default, or `"globe"`) and `config.space` (`false` by default, or `{ preset: "milkyway" }` for a
|
|
138
|
-
space background behind the globe) are passed to MapTiler only when set.
|
|
138
|
+
space background behind the globe) are passed to MapTiler only when set. `config.telemetry: false`
|
|
139
|
+
turns off MapTiler's telemetry (a usage report of the SDK version, API key, and session id, sent
|
|
140
|
+
about 2 s after each map is created) before the map is created. It is a page-wide setting in
|
|
141
|
+
MapTiler's SDK: once one map turns it off, it stays off for every map on the page. MapTiler's
|
|
142
|
+
session id (`mtsid`) stays on either way, since MapTiler bills SDK maps by session (without one,
|
|
143
|
+
every tile request counts on its own) and its tile cache needs it. By default (`true`) MapTiler's
|
|
144
|
+
own default applies.
|
|
139
145
|
|
|
140
146
|
Without `createMonitorsStore`, build a `DataSource` yourself (below).
|
|
141
147
|
|
|
@@ -207,7 +213,9 @@ the legend section's title: `layerLabel` (default) or the entry type's label fro
|
|
|
207
213
|
provider (`monitorsPlugin` uses the latter, so its section reads "PM2.5"; `layerLabel` still names
|
|
208
214
|
the layer in the list, selection bar and summary). `Legend` takes `order` (`"bottom-first"`,
|
|
209
215
|
default, or `"top-first"`) and `framed` (`true`, default, draws its own card; `false` draws no
|
|
210
|
-
border, background, padding or fixed width, so a host's own card frames it).
|
|
216
|
+
border, background, padding or fixed width, so a host's own card frames it). The framed legend
|
|
217
|
+
and the feature list panel are 21rem wide at most, and never wider than the map less 2rem (the
|
|
218
|
+
window less 2rem outside a `MapView`), so they fit a small map on a wide screen. Without a frame,
|
|
211
219
|
translucent swatches blend over the nearest ancestor with an opaque background (a see-through
|
|
212
220
|
card is skipped), else `--background`; that color is read on mount, when `framed` changes, and on
|
|
213
221
|
a theme change, but not when the card changes color through its own class or style.
|
|
@@ -254,7 +262,9 @@ source loads, fails, or is unavailable, the legend says so (a failure offers "Tr
|
|
|
254
262
|
calls `status.retry()`). Values are shown with the metadata's display precision, except in the
|
|
255
263
|
monitor, cluster, and collocation tooltips, which match monitor-map: the reading and a cluster's
|
|
256
264
|
average are whole numbers, and a cluster's max and min are as reported. Fire values are cut off
|
|
257
|
-
rather than rounded (see `truncateValues`).
|
|
265
|
+
rather than rounded (see `truncateValues`). On a category scale, a value that is its level's code
|
|
266
|
+
(smoke's `"light"`, the heat score's `2`) isn't shown: the list row, the tooltip, and the row's
|
|
267
|
+
accessible name give only the level label. Times in the tooltips read like sjvair.com's map, in
|
|
258
268
|
Pacific time: "4:00 pm Friday Nov 06, 2026", with " PT" after them on a device in another zone.
|
|
259
269
|
The monitor cluster tooltip's count line gives the area the monitors are within, "3 monitors
|
|
260
270
|
within a 22.619 mi² area": the area of their convex hull, left out when it would read 0.000 (two
|
|
@@ -548,6 +558,29 @@ local sjvair.com stack at `http://localhost:8000`.
|
|
|
548
558
|
|
|
549
559
|
## Changes
|
|
550
560
|
|
|
561
|
+
### 0.1.0-beta.7
|
|
562
|
+
|
|
563
|
+
- **Plugins come back at once after a basemap switch.** After picking another basemap, monitors,
|
|
564
|
+
fire, smoke and the other layers were missing until every tile of the new basemap had loaded
|
|
565
|
+
and drawn (4–10 s on a slow connection). They are now added as soon as the new style loads, as
|
|
566
|
+
sjvair.com's map does. A theme change while tiles are loading no longer waits either.
|
|
567
|
+
- **`config.telemetry: false`** turns off MapTiler's telemetry (its usage report) for the page,
|
|
568
|
+
before the map is created. Sessions stay on, so billing by session and the tile cache are
|
|
569
|
+
unchanged. By default nothing changes.
|
|
570
|
+
- **The feature list panel and the framed legend fit the map.** They were capped at the window's
|
|
571
|
+
width less 2rem; inside a `MapView` they are now capped at the map's width less 2rem, so a small
|
|
572
|
+
map on a wide screen doesn't overflow. MapView sets `--mapsdk-map-width` for this.
|
|
573
|
+
|
|
574
|
+
### 0.1.0-beta.6
|
|
575
|
+
|
|
576
|
+
- **A category reading is shown once.** When a feature's value is its category level's code, the
|
|
577
|
+
list row, the tooltip, and the row's accessible name show only the level label. Smoke rows read
|
|
578
|
+
"Smoke, Light" (was "light Light"), burn status "Burning Discouraged", and heat score
|
|
579
|
+
"Moderate" (the 0–4 number is no longer shown). Number scales such as PM2.5 and fire power are
|
|
580
|
+
unchanged, and a category value that matches no level is still shown.
|
|
581
|
+
- **New message key for hosts with their own catalog:** `mapsdk_list_row_level`
|
|
582
|
+
(`"{name}, {level}"`).
|
|
583
|
+
|
|
551
584
|
### 0.1.0-beta.5
|
|
552
585
|
|
|
553
586
|
- **Shapes that cover the view are in view.** The feature list and the view summary count a shape
|
|
@@ -32,6 +32,14 @@ export interface MapConfig {
|
|
|
32
32
|
space?: false | {
|
|
33
33
|
preset: string;
|
|
34
34
|
};
|
|
35
|
+
/**
|
|
36
|
+
* Default `true`: MapTiler's own default. `false` turns off MapTiler's telemetry (a usage
|
|
37
|
+
* report of the SDK version, the API key, and the session id, sent about 2 s after each map is
|
|
38
|
+
* created) before the GL map is created. It is a page-wide setting in MapTiler's SDK, so once
|
|
39
|
+
* one map turns it off it stays off for every map on the page. MapTiler's session id (`mtsid`)
|
|
40
|
+
* stays on: MapTiler bills SDK maps by session, and its tile cache needs sessions.
|
|
41
|
+
*/
|
|
42
|
+
telemetry?: boolean;
|
|
35
43
|
}
|
|
36
44
|
export interface CreateMapOptions {
|
|
37
45
|
config: MapConfig;
|
|
@@ -381,7 +381,9 @@ class MapContextImpl {
|
|
|
381
381
|
this.#host.attach(gl);
|
|
382
382
|
this.#router.attach(gl);
|
|
383
383
|
gl.on("moveend", this.#onMoveEnd);
|
|
384
|
-
this.basemap.attach(gl,
|
|
384
|
+
this.basemap.attach(gl,
|
|
385
|
+
// Called from style.load: the style is loaded, though its tiles may not be.
|
|
386
|
+
() => this.#host.restyle(true), () => {
|
|
385
387
|
this.#ready = false;
|
|
386
388
|
this.#readyRequested = false;
|
|
387
389
|
this.#host.suspend();
|
|
@@ -57,11 +57,15 @@ export declare class PluginHost {
|
|
|
57
57
|
*/
|
|
58
58
|
suspend(): void;
|
|
59
59
|
/**
|
|
60
|
-
* Tears down and sets up every plugin again (after a basemap change), once the style
|
|
61
|
-
*
|
|
62
|
-
*
|
|
60
|
+
* Tears down and sets up every plugin again (after a basemap or theme change), once the style
|
|
61
|
+
* loads. Pass `styleLoaded` from a `style.load` listener: `isStyleLoaded()` stays false there
|
|
62
|
+
* until every tile has loaded, and `style.load` has already fired, so waiting would last until
|
|
63
|
+
* `idle`. A live host's style is loaded too (`suspend` comes before every `setStyle`), so a theme
|
|
64
|
+
* change sets up at once as well. On a loaded style the setup is synchronous, so only map events
|
|
65
|
+
* and theme watching call this, never code inside an effect: roots created there can stop (see
|
|
66
|
+
* the class comment).
|
|
63
67
|
*/
|
|
64
|
-
restyle(): void;
|
|
68
|
+
restyle(styleLoaded?: boolean): void;
|
|
65
69
|
/** Tears down every plugin. The gl is not touched after this returns. */
|
|
66
70
|
detach(): void;
|
|
67
71
|
}
|
|
@@ -161,15 +161,26 @@ export class PluginHost {
|
|
|
161
161
|
this.#teardownAll();
|
|
162
162
|
}
|
|
163
163
|
/**
|
|
164
|
-
* Tears down and sets up every plugin again (after a basemap change), once the style
|
|
165
|
-
*
|
|
166
|
-
*
|
|
164
|
+
* Tears down and sets up every plugin again (after a basemap or theme change), once the style
|
|
165
|
+
* loads. Pass `styleLoaded` from a `style.load` listener: `isStyleLoaded()` stays false there
|
|
166
|
+
* until every tile has loaded, and `style.load` has already fired, so waiting would last until
|
|
167
|
+
* `idle`. A live host's style is loaded too (`suspend` comes before every `setStyle`), so a theme
|
|
168
|
+
* change sets up at once as well. On a loaded style the setup is synchronous, so only map events
|
|
169
|
+
* and theme watching call this, never code inside an effect: roots created there can stop (see
|
|
170
|
+
* the class comment).
|
|
167
171
|
*/
|
|
168
|
-
restyle() {
|
|
172
|
+
restyle(styleLoaded = false) {
|
|
169
173
|
if (!this.#gl)
|
|
170
174
|
return;
|
|
175
|
+
const loaded = styleLoaded || this.#live;
|
|
171
176
|
this.#teardownAll();
|
|
172
|
-
|
|
177
|
+
if (loaded) {
|
|
178
|
+
this.#cancelStyleWait();
|
|
179
|
+
this.#setupAll();
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
this.#whenStyleLoaded(true);
|
|
183
|
+
}
|
|
173
184
|
}
|
|
174
185
|
/** Tears down every plugin. The gl is not touched after this returns. */
|
|
175
186
|
detach() {
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
"mapsdk_list_row": "{name}, {value} {units}, {level}",
|
|
9
9
|
"mapsdk_list_row_no_units": "{name}, {value}, {level}",
|
|
10
10
|
"mapsdk_list_row_no_value": "{name}, no reading",
|
|
11
|
+
"mapsdk_list_row_level": "{name}, {level}",
|
|
11
12
|
"mapsdk_list_row_detail": "{name}, {detail}",
|
|
12
13
|
"mapsdk_list_empty": "Nothing on the map in this view",
|
|
13
14
|
"mapsdk_selected_count_one": "{count} selected",
|
|
@@ -11,11 +11,11 @@
|
|
|
11
11
|
rowLabel,
|
|
12
12
|
levelText,
|
|
13
13
|
shownLevel,
|
|
14
|
+
shownValue,
|
|
14
15
|
sortItems,
|
|
15
16
|
typeaheadIndex,
|
|
16
17
|
type SortBy
|
|
17
18
|
} from "./feature-list.js";
|
|
18
|
-
import { formatValue as formatNumber } from "./value-format.js";
|
|
19
19
|
|
|
20
20
|
interface Props {
|
|
21
21
|
map: MapContext;
|
|
@@ -267,13 +267,6 @@
|
|
|
267
267
|
pick(rows[index], e.shiftKey || e.metaKey || e.ctrlKey);
|
|
268
268
|
}
|
|
269
269
|
|
|
270
|
-
function formatValue(row: Row): string {
|
|
271
|
-
if (row.value === null) return "";
|
|
272
|
-
return typeof row.value === "number"
|
|
273
|
-
? formatNumber(row.value, row.precision, map.format, row.truncate)
|
|
274
|
-
: row.value;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
270
|
onDestroy(() => {
|
|
278
271
|
clearTimeout(typeaheadTimer);
|
|
279
272
|
clearTimeout(menuSentTimer);
|
|
@@ -332,6 +325,7 @@
|
|
|
332
325
|
{@const index = range.start + i}
|
|
333
326
|
{@const level = shownLevel(row)}
|
|
334
327
|
{@const levelPlace = levelText(row, map.messages)}
|
|
328
|
+
{@const value = shownValue(row, map.format)}
|
|
335
329
|
{@const isOn = isSelected(row)}
|
|
336
330
|
<!-- Options aren't focused: the listbox holds focus and handles keys (aria-activedescendant). -->
|
|
337
331
|
<!-- svelte-ignore a11y_click_events_have_key_events, a11y_interactive_supports_focus -->
|
|
@@ -359,7 +353,7 @@
|
|
|
359
353
|
</span>
|
|
360
354
|
<span class="mapsdk-list-name">{row.name}</span>
|
|
361
355
|
<span class="mapsdk-list-value">
|
|
362
|
-
{
|
|
356
|
+
{value ?? ""}{#if row.units && value !== null} {row.units}{/if}
|
|
363
357
|
</span>
|
|
364
358
|
<span class="mapsdk-list-level text-xs">
|
|
365
359
|
{#if levelPlace.kind !== "detail"}
|
|
@@ -387,7 +381,8 @@
|
|
|
387
381
|
flex-direction: column;
|
|
388
382
|
position: relative;
|
|
389
383
|
width: 21rem;
|
|
390
|
-
|
|
384
|
+
/* The map's width inside a MapView (set there), else the window's. */
|
|
385
|
+
max-width: calc(var(--mapsdk-map-width, 100vw) - 2rem);
|
|
391
386
|
max-height: 100%;
|
|
392
387
|
box-sizing: border-box;
|
|
393
388
|
border: 1px solid var(--card-border, var(--border));
|
package/dist/ui/Legend.svelte
CHANGED
|
@@ -143,7 +143,8 @@
|
|
|
143
143
|
flex-direction: column;
|
|
144
144
|
gap: 0.75rem;
|
|
145
145
|
width: 21rem;
|
|
146
|
-
|
|
146
|
+
/* The map's width inside a MapView (set there), else the window's. */
|
|
147
|
+
max-width: calc(var(--mapsdk-map-width, 100vw) - 2rem);
|
|
147
148
|
box-sizing: border-box;
|
|
148
149
|
padding: 0.75rem 1rem;
|
|
149
150
|
border: 1px solid var(--card-border, var(--border));
|
|
@@ -3,9 +3,8 @@
|
|
|
3
3
|
import type { MessageProvider } from "../data/messages.js";
|
|
4
4
|
import type { FeatureListItem } from "../core/plugin.js";
|
|
5
5
|
import { NO_LEVEL } from "../data/scale.js";
|
|
6
|
-
import { levelText } from "./feature-list.js";
|
|
6
|
+
import { levelText, shownValue } from "./feature-list.js";
|
|
7
7
|
import { levelChip } from "./level-chip.js";
|
|
8
|
-
import { formatValue } from "./value-format.js";
|
|
9
8
|
|
|
10
9
|
// The generic tooltip body under a feature's name: its value and a chip with its level. Shared
|
|
11
10
|
// by MapTooltip and by plugins that show it for features they don't list.
|
|
@@ -26,13 +25,8 @@
|
|
|
26
25
|
const chip = levelChip(level?.color);
|
|
27
26
|
return chip ? `background:${chip.background};color:${chip.text}` : "";
|
|
28
27
|
});
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
? null
|
|
32
|
-
: typeof item.value === "number"
|
|
33
|
-
? formatValue(item.value, item.precision, format, item.truncate)
|
|
34
|
-
: item.value
|
|
35
|
-
);
|
|
28
|
+
// A category reading shows only its level chip: its value is the level's code.
|
|
29
|
+
const valueText = $derived(shownValue(item, format));
|
|
36
30
|
</script>
|
|
37
31
|
|
|
38
32
|
<div class="mapsdk-tooltip-item">
|
|
@@ -26,6 +26,11 @@ export declare function inView(items: FeatureListItem[], bounds: Bounds): Featur
|
|
|
26
26
|
* label on a neutral swatch.
|
|
27
27
|
*/
|
|
28
28
|
export declare function shownLevel(item: FeatureListItem): Level | null;
|
|
29
|
+
/**
|
|
30
|
+
* The value as shown, or `null` for none. A category reading, whose value is its level's code
|
|
31
|
+
* (smoke's "light", the heat score's 2), shows no value: its level label already says it.
|
|
32
|
+
*/
|
|
33
|
+
export declare function shownValue(item: FeatureListItem, format: FormatProvider): string | null;
|
|
29
34
|
/** A sorted copy. `value`: highest first, null values and no level last; ties and `name` by name. */
|
|
30
35
|
export declare function sortItems(items: FeatureListItem[], by: SortBy): FeatureListItem[];
|
|
31
36
|
/** The first index at or after `from` (wrapping) whose name starts with `query`, any case; -1 if none. */
|
package/dist/ui/feature-list.js
CHANGED
|
@@ -29,6 +29,20 @@ export function inView(items, bounds) {
|
|
|
29
29
|
export function shownLevel(item) {
|
|
30
30
|
return item.level !== NO_LEVEL ? item.level : null;
|
|
31
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* The value as shown, or `null` for none. A category reading, whose value is its level's code
|
|
34
|
+
* (smoke's "light", the heat score's 2), shows no value: its level label already says it.
|
|
35
|
+
*/
|
|
36
|
+
export function shownValue(item, format) {
|
|
37
|
+
if (item.value === null)
|
|
38
|
+
return null;
|
|
39
|
+
const level = shownLevel(item);
|
|
40
|
+
if (level && level.value !== null && level.value === item.value)
|
|
41
|
+
return null;
|
|
42
|
+
return typeof item.value === "number"
|
|
43
|
+
? formatValue(item.value, item.precision, format, item.truncate)
|
|
44
|
+
: item.value;
|
|
45
|
+
}
|
|
32
46
|
const collator = new Intl.Collator("en-US");
|
|
33
47
|
/** A value's sort weight; `null` sorts last. Category values sort by their level's tier. */
|
|
34
48
|
function weight(item) {
|
|
@@ -82,16 +96,18 @@ export function levelText(item, messages) {
|
|
|
82
96
|
/** A row's accessible name: name, value, units, and level label (or detail). */
|
|
83
97
|
export function rowLabel(item, messages, format) {
|
|
84
98
|
const level = levelText(item, messages);
|
|
85
|
-
|
|
99
|
+
const value = shownValue(item, format);
|
|
100
|
+
if (value === null) {
|
|
101
|
+
// A category reading: its level is its value.
|
|
102
|
+
if (item.value !== null && level.kind === "level")
|
|
103
|
+
return messages("mapsdk_list_row_level", { name: item.name, level: level.text });
|
|
86
104
|
return level.kind === "detail"
|
|
87
105
|
? messages("mapsdk_list_row_detail", { name: item.name, detail: level.text })
|
|
88
106
|
: messages("mapsdk_list_row_no_value", { name: item.name });
|
|
89
107
|
}
|
|
90
108
|
const params = {
|
|
91
109
|
name: item.name,
|
|
92
|
-
value
|
|
93
|
-
? formatValue(item.value, item.precision, format, item.truncate)
|
|
94
|
-
: item.value,
|
|
110
|
+
value,
|
|
95
111
|
units: item.units,
|
|
96
112
|
level: level.text
|
|
97
113
|
};
|
package/dist/view/MapView.svelte
CHANGED
|
@@ -250,6 +250,7 @@
|
|
|
250
250
|
role="region"
|
|
251
251
|
aria-label={label ?? map.messages("mapsdk_map_label")}
|
|
252
252
|
data-mapsdk-map={map.uid}
|
|
253
|
+
style:--mapsdk-map-width={width > 0 ? `${width}px` : undefined}
|
|
253
254
|
>
|
|
254
255
|
<div class="mapsdk-gl" bind:this={glContainer}></div>
|
|
255
256
|
<MapTooltip {map} {project} {width} {height} mode={tooltip} gl={projector} />
|
|
@@ -128,6 +128,10 @@ function recordView(gl, ctx) {
|
|
|
128
128
|
*/
|
|
129
129
|
export function createGlMap(container, ctx, engineControls = false) {
|
|
130
130
|
const { camera, style } = untrack(() => ({ camera: ctx.camera, style: styleOf(ctx) }));
|
|
131
|
+
// Before the map exists, so its telemetry timer finds it off. Sessions stay on: MapTiler
|
|
132
|
+
// bills SDK maps by session, and without one every tile request counts on its own.
|
|
133
|
+
if (ctx.config.telemetry === false)
|
|
134
|
+
maptilerConfig.telemetry = false;
|
|
131
135
|
const gl = new GlMap({
|
|
132
136
|
container,
|
|
133
137
|
style,
|