@sjvair/monitor-map 4.2.0 → 5.0.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +61 -126
- package/dist/lib/MonitorDetailPanel.svelte +23 -20
- package/dist/lib/MonitorMapLayout.svelte +147 -77
- package/dist/lib/abort.d.ts +16 -0
- package/dist/lib/abort.js +51 -0
- package/dist/lib/components/DataBox.svelte +4 -1
- package/dist/lib/components/ui/range-calendar/range-calendar.svelte.d.ts +1 -1
- package/dist/lib/config.svelte.d.ts +7 -0
- package/dist/lib/config.svelte.js +10 -0
- package/dist/lib/core.d.ts +2 -2
- package/dist/lib/core.js +2 -1
- package/dist/lib/data-chart/DataChart.svelte +54 -24
- package/dist/lib/data-chart/DateRange.d.ts +13 -4
- package/dist/lib/data-chart/DateRange.js +50 -14
- package/dist/lib/data-chart/chart-utils.js +5 -7
- package/dist/lib/data-chart/level-colors.d.ts +3 -4
- package/dist/lib/data-chart/level-colors.js +7 -4
- package/dist/lib/data-chart/pacific-time.d.ts +9 -0
- package/dist/lib/data-chart/pacific-time.js +25 -0
- package/dist/lib/data-chart/requests.d.ts +2 -1
- package/dist/lib/data-chart/requests.js +5 -2
- package/dist/lib/data-chart/tooltip.js +2 -2
- package/{src/lib/ev-stations/components/EvStationTooltip.svelte → dist/lib/ev-stations/EvPopup.svelte} +6 -5
- package/dist/lib/ev-stations/EvPopup.svelte.d.ts +7 -0
- package/dist/lib/ev-stations/ev-plugin.svelte.d.ts +14 -0
- package/dist/lib/ev-stations/ev-plugin.svelte.js +71 -0
- package/dist/lib/ev-stations/ev-station-icons.d.ts +1 -4
- package/dist/lib/ev-stations/ev-station-icons.js +1 -13
- package/dist/lib/ev-stations/ev-stations.d.ts +7 -0
- package/dist/lib/ev-stations/ev-stations.js +53 -0
- package/dist/lib/ev-stations/ev-store.svelte.d.ts +21 -0
- package/dist/lib/ev-stations/ev-store.svelte.js +83 -0
- package/dist/lib/ev-stations/station-popup.svelte.d.ts +14 -0
- package/dist/lib/ev-stations/station-popup.svelte.js +54 -0
- package/dist/lib/ev-stations/types.d.ts +0 -29
- package/dist/lib/ev-stations/zip-codes.d.ts +11 -0
- package/dist/lib/ev-stations/zip-codes.js +28 -16
- package/dist/lib/index.d.ts +8 -56
- package/dist/lib/index.js +7 -46
- package/dist/lib/legend/Legend.svelte +18 -19
- package/dist/lib/legend/Legend.svelte.d.ts +2 -2
- package/dist/lib/levels.d.ts +7 -0
- package/dist/lib/levels.js +19 -0
- package/dist/lib/map/MapShell.svelte +17 -24
- package/dist/lib/map/MapShell.svelte.d.ts +4 -4
- package/dist/lib/map/basemaps.d.ts +25 -0
- package/dist/lib/map/basemaps.js +66 -0
- package/dist/lib/map/live-map.svelte.d.ts +3 -0
- package/dist/lib/map/live-map.svelte.js +22 -0
- package/dist/lib/map/sync-plugins.svelte.d.ts +19 -0
- package/dist/lib/map/sync-plugins.svelte.js +52 -0
- package/dist/lib/monitors/components/MonitorSubscriptionManager.svelte +82 -82
- package/dist/lib/monitors/filtered-source.svelte.d.ts +9 -0
- package/dist/lib/monitors/filtered-source.svelte.js +28 -0
- package/dist/lib/monitors/monitor-details.svelte.js +11 -3
- package/dist/lib/monitors/monitors.svelte.d.ts +29 -26
- package/dist/lib/monitors/monitors.svelte.js +35 -126
- package/dist/lib/monitors/retry-load.d.ts +8 -0
- package/dist/lib/monitors/retry-load.js +11 -0
- package/dist/lib/monitors/subscriptions.d.ts +3 -0
- package/dist/lib/monitors/subscriptions.js +9 -0
- package/dist/lib/monitors/types.d.ts +1 -41
- package/dist/lib/options/map-option-defaults.d.ts +25 -36
- package/dist/lib/options/map-option-defaults.js +12 -30
- package/dist/lib/options/map-options.svelte.d.ts +13 -0
- package/dist/lib/options/map-options.svelte.js +26 -0
- package/dist/lib/options/menus/LayersMenu.svelte +8 -20
- package/dist/lib/options/menus/LayersMenu.svelte.d.ts +17 -2
- package/dist/lib/options/menus/MonitorsMenu.svelte +34 -18
- package/dist/lib/options/menus/OverlaysMenu.svelte +2 -2
- package/dist/lib/options/menus/PollutantMenu.svelte +4 -9
- package/dist/lib/options/menus/SettingsMenu.svelte +32 -25
- package/dist/lib/options/menus/SettingsMenu.svelte.d.ts +5 -16
- package/dist/lib/options/plugin-order.d.ts +8 -0
- package/dist/lib/options/plugin-order.js +36 -0
- package/dist/lib/options/pollutant-param.d.ts +7 -5
- package/dist/lib/options/pollutant-param.js +9 -5
- package/dist/lib/options/row-icons.d.ts +8 -0
- package/dist/lib/options/row-icons.js +15 -0
- package/dist/lib/options/rows/MarkerIcon.svelte +5 -5
- package/dist/lib/options/rows/MarkerIcon.svelte.d.ts +1 -2
- package/dist/lib/search/Search.svelte +20 -33
- package/dist/lib/search/Search.svelte.d.ts +5 -1
- package/dist/lib/search/search-pin.svelte.d.ts +11 -0
- package/dist/lib/search/search-pin.svelte.js +28 -0
- package/dist/lib/search/service.d.ts +7 -2
- package/dist/lib/search/service.js +17 -10
- package/dist/lib/stores.svelte.d.ts +8 -0
- package/dist/lib/stores.svelte.js +9 -0
- package/dist/lib/wind/wind-plugin.d.ts +11 -0
- package/dist/lib/wind/wind-plugin.js +25 -0
- package/package.json +12 -10
- package/src/lib/MonitorDetailPanel.svelte +23 -20
- package/src/lib/MonitorMapLayout.svelte +147 -77
- package/src/lib/abort.ts +59 -0
- package/src/lib/components/DataBox.svelte +4 -1
- package/src/lib/config.svelte.ts +16 -0
- package/src/lib/core.ts +2 -2
- package/src/lib/data-chart/DataChart.svelte +54 -24
- package/src/lib/data-chart/DateRange.ts +58 -15
- package/src/lib/data-chart/chart-utils.ts +6 -7
- package/src/lib/data-chart/level-colors.ts +8 -9
- package/src/lib/data-chart/pacific-time.ts +31 -0
- package/src/lib/data-chart/requests.ts +14 -7
- package/src/lib/data-chart/tooltip.ts +2 -2
- package/{dist/lib/ev-stations/components/EvStationTooltip.svelte → src/lib/ev-stations/EvPopup.svelte} +6 -5
- package/src/lib/ev-stations/ev-plugin.svelte.ts +86 -0
- package/src/lib/ev-stations/ev-station-icons.ts +1 -17
- package/src/lib/ev-stations/ev-stations.ts +72 -0
- package/src/lib/ev-stations/ev-store.svelte.ts +117 -0
- package/src/lib/ev-stations/station-popup.svelte.ts +64 -0
- package/src/lib/ev-stations/types.ts +0 -34
- package/src/lib/ev-stations/zip-codes.ts +29 -16
- package/src/lib/index.ts +8 -99
- package/src/lib/legend/Legend.svelte +18 -19
- package/src/lib/levels.ts +24 -0
- package/src/lib/map/MapShell.svelte +17 -24
- package/src/lib/map/basemaps.ts +76 -0
- package/src/lib/map/live-map.svelte.ts +24 -0
- package/src/lib/map/sync-plugins.svelte.ts +69 -0
- package/src/lib/monitors/components/MonitorSubscriptionManager.svelte +82 -82
- package/src/lib/monitors/filtered-source.svelte.ts +38 -0
- package/src/lib/monitors/monitor-details.svelte.ts +12 -3
- package/src/lib/monitors/monitors.svelte.ts +62 -153
- package/src/lib/monitors/retry-load.ts +14 -0
- package/src/lib/monitors/subscriptions.ts +17 -0
- package/src/lib/monitors/types.ts +1 -47
- package/src/lib/options/map-option-defaults.ts +30 -53
- package/src/lib/options/map-options.svelte.ts +30 -0
- package/src/lib/options/menus/LayersMenu.svelte +8 -20
- package/src/lib/options/menus/MonitorsMenu.svelte +34 -18
- package/src/lib/options/menus/OverlaysMenu.svelte +2 -2
- package/src/lib/options/menus/PollutantMenu.svelte +4 -9
- package/src/lib/options/menus/SettingsMenu.svelte +32 -25
- package/src/lib/options/plugin-order.ts +38 -0
- package/src/lib/options/pollutant-param.ts +14 -9
- package/src/lib/options/row-icons.ts +17 -0
- package/src/lib/options/rows/MarkerIcon.svelte +5 -5
- package/src/lib/search/Search.svelte +20 -33
- package/src/lib/search/search-pin.svelte.ts +35 -0
- package/src/lib/search/service.ts +34 -15
- package/src/lib/stores.svelte.ts +15 -0
- package/src/lib/wind/wind-plugin.ts +33 -0
- package/dist/lib/collocation-sites/CollocationTooltip.svelte +0 -98
- package/dist/lib/collocation-sites/CollocationTooltip.svelte.d.ts +0 -7
- package/dist/lib/collocation-sites/collocations-icon-manager.svelte.d.ts +0 -7
- package/dist/lib/collocation-sites/collocations-icon-manager.svelte.js +0 -18
- package/dist/lib/collocation-sites/collocations-map-integration.svelte.d.ts +0 -21
- package/dist/lib/collocation-sites/collocations-map-integration.svelte.js +0 -113
- package/dist/lib/collocation-sites/collocations.svelte.d.ts +0 -9
- package/dist/lib/collocation-sites/collocations.svelte.js +0 -17
- package/dist/lib/collocation-sites/types.d.ts +0 -8
- package/dist/lib/collocation-sites/types.js +0 -1
- package/dist/lib/ev-stations/components/EvStationClusterTooltip.svelte +0 -16
- package/dist/lib/ev-stations/components/EvStationClusterTooltip.svelte.d.ts +0 -7
- package/dist/lib/ev-stations/components/EvStationTooltip.svelte.d.ts +0 -7
- package/dist/lib/ev-stations/ev-stations-cluster-renderer.d.ts +0 -19
- package/dist/lib/ev-stations/ev-stations-cluster-renderer.js +0 -131
- package/dist/lib/ev-stations/ev-stations-map-integration.svelte.d.ts +0 -31
- package/dist/lib/ev-stations/ev-stations-map-integration.svelte.js +0 -226
- package/dist/lib/ev-stations/ev-stations.svelte.d.ts +0 -9
- package/dist/lib/ev-stations/ev-stations.svelte.js +0 -55
- package/dist/lib/hms/hms-fire-icon-manager.d.ts +0 -13
- package/dist/lib/hms/hms-fire-icon-manager.js +0 -39
- package/dist/lib/hms/hms-fire-map-integration.svelte.d.ts +0 -23
- package/dist/lib/hms/hms-fire-map-integration.svelte.js +0 -94
- package/dist/lib/hms/hms-smoke-map-integration.svelte.d.ts +0 -39
- package/dist/lib/hms/hms-smoke-map-integration.svelte.js +0 -87
- package/dist/lib/hms/hms.svelte.d.ts +0 -19
- package/dist/lib/hms/hms.svelte.js +0 -227
- package/dist/lib/legend/LegendBar.svelte +0 -45
- package/dist/lib/legend/LegendBar.svelte.d.ts +0 -7
- package/dist/lib/legend/legend-data.d.ts +0 -40
- package/dist/lib/legend/legend-data.js +0 -65
- package/dist/lib/map/Map.svelte +0 -13
- package/dist/lib/map/Map.svelte.d.ts +0 -8
- package/dist/lib/map/basemap.d.ts +0 -14
- package/dist/lib/map/basemap.js +0 -10
- package/dist/lib/map/icons.d.ts +0 -7
- package/dist/lib/map/icons.js +0 -33
- package/dist/lib/map/integrations/base-layer-seperator.d.ts +0 -9
- package/dist/lib/map/integrations/base-layer-seperator.js +0 -11
- package/dist/lib/map/integrations/click-manager.d.ts +0 -14
- package/dist/lib/map/integrations/click-manager.js +0 -52
- package/dist/lib/map/integrations/integrations-manager.d.ts +0 -8
- package/dist/lib/map/integrations/integrations-manager.js +0 -20
- package/dist/lib/map/integrations/map-display-option.svelte.d.ts +0 -7
- package/dist/lib/map/integrations/map-display-option.svelte.js +0 -10
- package/dist/lib/map/integrations/map-geojson-integration.svelte.d.ts +0 -32
- package/dist/lib/map/integrations/map-geojson-integration.svelte.js +0 -60
- package/dist/lib/map/integrations/map-icon-manager.d.ts +0 -15
- package/dist/lib/map/integrations/map-icon-manager.js +0 -52
- package/dist/lib/map/integrations/map-integration.svelte.d.ts +0 -6
- package/dist/lib/map/integrations/map-integration.svelte.js +0 -2
- package/dist/lib/map/integrations/map-layer-integration.svelte.d.ts +0 -11
- package/dist/lib/map/integrations/map-layer-integration.svelte.js +0 -40
- package/dist/lib/map/integrations/tooltip.svelte.d.ts +0 -36
- package/dist/lib/map/integrations/tooltip.svelte.js +0 -131
- package/dist/lib/map/integrations/types.d.ts +0 -14
- package/dist/lib/map/integrations/types.js +0 -1
- package/dist/lib/map/map-manager.svelte.d.ts +0 -20
- package/dist/lib/map/map-manager.svelte.js +0 -63
- package/dist/lib/map/map-style-state.svelte.d.ts +0 -23
- package/dist/lib/map/map-style-state.svelte.js +0 -86
- package/dist/lib/map/utils.d.ts +0 -7
- package/dist/lib/map/utils.js +0 -36
- package/dist/lib/monitors/components/MonitorClusterTooltip.svelte +0 -112
- package/dist/lib/monitors/components/MonitorClusterTooltip.svelte.d.ts +0 -7
- package/dist/lib/monitors/components/MonitorTooltip.svelte +0 -61
- package/dist/lib/monitors/components/MonitorTooltip.svelte.d.ts +0 -7
- package/dist/lib/monitors/monitor-utils.d.ts +0 -4
- package/dist/lib/monitors/monitor-utils.js +0 -41
- package/dist/lib/monitors/monitors-cluster-renderer.d.ts +0 -33
- package/dist/lib/monitors/monitors-cluster-renderer.js +0 -221
- package/dist/lib/monitors/monitors-icon-manager.svelte.d.ts +0 -15
- package/dist/lib/monitors/monitors-icon-manager.svelte.js +0 -78
- package/dist/lib/monitors/monitors-map-integration.svelte.d.ts +0 -43
- package/dist/lib/monitors/monitors-map-integration.svelte.js +0 -301
- package/dist/lib/options/reset-map-options.d.ts +0 -2
- package/dist/lib/options/reset-map-options.js +0 -24
- package/dist/lib/reactivity.svelte.d.ts +0 -0
- package/dist/lib/reactivity.svelte.js +0 -83
- package/dist/lib/wind/wind.svelte.d.ts +0 -10
- package/dist/lib/wind/wind.svelte.js +0 -13
- package/src/lib/collocation-sites/CollocationTooltip.svelte +0 -98
- package/src/lib/collocation-sites/collocations-icon-manager.svelte.ts +0 -21
- package/src/lib/collocation-sites/collocations-map-integration.svelte.ts +0 -136
- package/src/lib/collocation-sites/collocations.svelte.ts +0 -24
- package/src/lib/collocation-sites/types.ts +0 -10
- package/src/lib/ev-stations/components/EvStationClusterTooltip.svelte +0 -16
- package/src/lib/ev-stations/ev-stations-cluster-renderer.ts +0 -162
- package/src/lib/ev-stations/ev-stations-map-integration.svelte.ts +0 -248
- package/src/lib/ev-stations/ev-stations.svelte.ts +0 -79
- package/src/lib/hms/hms-fire-icon-manager.ts +0 -48
- package/src/lib/hms/hms-fire-map-integration.svelte.ts +0 -109
- package/src/lib/hms/hms-smoke-map-integration.svelte.ts +0 -105
- package/src/lib/hms/hms.svelte.ts +0 -264
- package/src/lib/legend/LegendBar.svelte +0 -45
- package/src/lib/legend/legend-data.ts +0 -101
- package/src/lib/map/Map.svelte +0 -13
- package/src/lib/map/basemap.ts +0 -24
- package/src/lib/map/icons.ts +0 -42
- package/src/lib/map/integrations/base-layer-seperator.ts +0 -17
- package/src/lib/map/integrations/click-manager.ts +0 -64
- package/src/lib/map/integrations/integrations-manager.ts +0 -25
- package/src/lib/map/integrations/map-display-option.svelte.ts +0 -13
- package/src/lib/map/integrations/map-geojson-integration.svelte.ts +0 -77
- package/src/lib/map/integrations/map-icon-manager.ts +0 -67
- package/src/lib/map/integrations/map-integration.svelte.ts +0 -8
- package/src/lib/map/integrations/map-layer-integration.svelte.ts +0 -49
- package/src/lib/map/integrations/tooltip.svelte.ts +0 -153
- package/src/lib/map/integrations/types.ts +0 -24
- package/src/lib/map/map-manager.svelte.ts +0 -83
- package/src/lib/map/map-style-state.svelte.ts +0 -94
- package/src/lib/map/utils.ts +0 -53
- package/src/lib/monitors/components/MonitorClusterTooltip.svelte +0 -112
- package/src/lib/monitors/components/MonitorTooltip.svelte +0 -61
- package/src/lib/monitors/monitor-utils.ts +0 -57
- package/src/lib/monitors/monitors-cluster-renderer.ts +0 -285
- package/src/lib/monitors/monitors-icon-manager.svelte.ts +0 -98
- package/src/lib/monitors/monitors-map-integration.svelte.ts +0 -318
- package/src/lib/options/reset-map-options.ts +0 -28
- package/src/lib/reactivity.svelte.ts +0 -82
- package/src/lib/wind/wind.svelte.ts +0 -20
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The app-wide data stores, created once. This module imports only `@sjvair/map-sdk/stores`, so
|
|
3
|
+
* the light `/core` entry can reach it without loading the map engine.
|
|
4
|
+
*/
|
|
5
|
+
export declare const monitorsStore: import("@sjvair/map-sdk/stores").MonitorsStore;
|
|
6
|
+
export declare const hmsSmokeStore: import("@sjvair/map-sdk/stores").HmsSmokeStore;
|
|
7
|
+
export declare const hmsFireStore: import("@sjvair/map-sdk/stores").HmsFireStore;
|
|
8
|
+
export declare const collocationSitesStore: import("@sjvair/map-sdk/stores").CollocationSitesStore;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { createCollocationSitesStore, createHmsFireStore, createHmsSmokeStore, createMonitorsStore } from "@sjvair/map-sdk/stores";
|
|
2
|
+
/**
|
|
3
|
+
* The app-wide data stores, created once. This module imports only `@sjvair/map-sdk/stores`, so
|
|
4
|
+
* the light `/core` entry can reach it without loading the map engine.
|
|
5
|
+
*/
|
|
6
|
+
export const monitorsStore = createMonitorsStore();
|
|
7
|
+
export const hmsSmokeStore = createHmsSmokeStore();
|
|
8
|
+
export const hmsFireStore = createHmsFireStore();
|
|
9
|
+
export const collocationSitesStore = createCollocationSitesStore({ monitors: monitorsStore });
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type CustomLayerInterface } from "@maptiler/sdk";
|
|
2
|
+
import type { MapPlugin } from "@sjvair/map-sdk";
|
|
3
|
+
export interface WindPluginOptions {
|
|
4
|
+
/** Builds the layer; runs again on each restyle. Defaults to a `@maptiler/weather` WindLayer. */
|
|
5
|
+
createLayer?: (id: string) => CustomLayerInterface;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Wind particles as a map-sdk custom layer. `@maptiler/weather` can't be a map-sdk dependency, so
|
|
9
|
+
* the host adds it here; only the layout chunk reaches this module.
|
|
10
|
+
*/
|
|
11
|
+
export declare function windPlugin(options?: WindPluginOptions): MapPlugin;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { config } from "@maptiler/sdk";
|
|
2
|
+
import { WindLayer } from "@maptiler/weather";
|
|
3
|
+
import { monitorMapConfig } from "../config.svelte.js";
|
|
4
|
+
// The weather layer fetches its data with the SDK's global key, which map-sdk (it passes the key
|
|
5
|
+
// to the map only) never sets.
|
|
6
|
+
const defaultCreateLayer = (id) => {
|
|
7
|
+
config.apiKey = monitorMapConfig().maptilerKey;
|
|
8
|
+
return new WindLayer({ id, opacity: 0.5 });
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Wind particles as a map-sdk custom layer. `@maptiler/weather` can't be a map-sdk dependency, so
|
|
12
|
+
* the host adds it here; only the layout chunk reaches this module.
|
|
13
|
+
*/
|
|
14
|
+
export function windPlugin(options = {}) {
|
|
15
|
+
const createLayer = options.createLayer ?? defaultCreateLayer;
|
|
16
|
+
return {
|
|
17
|
+
id: "wind",
|
|
18
|
+
label: "Wind",
|
|
19
|
+
selectable: false,
|
|
20
|
+
setup(ctx) {
|
|
21
|
+
ctx.addCustomLayer("wind", createLayer);
|
|
22
|
+
return () => { };
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sjvair/monitor-map",
|
|
3
3
|
"description": "SJVAir's interactive monitor map",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "5.0.0-beta.0",
|
|
5
|
+
"license": "MIT",
|
|
5
6
|
"repository": {
|
|
6
7
|
"type": "git",
|
|
7
8
|
"url": "https://github.com/SJVAir/monitor-map"
|
|
@@ -17,6 +18,7 @@
|
|
|
17
18
|
"format": "prettier --write .",
|
|
18
19
|
"lint": "prettier --check . && eslint .",
|
|
19
20
|
"test": "vitest run",
|
|
21
|
+
"map-sdk:pack": "bash scripts/pack-map-sdk.sh",
|
|
20
22
|
"test:bundle": "npm run build:lib && node tests/bundle/check-bundle.mjs"
|
|
21
23
|
},
|
|
22
24
|
"type": "module",
|
|
@@ -48,24 +50,28 @@
|
|
|
48
50
|
],
|
|
49
51
|
"peerDependencies": {
|
|
50
52
|
"@internationalized/date": "^3.12.4",
|
|
53
|
+
"@sjvair/sdk": "npm:@jsr/sjvair__sdk@^5.0.0-beta.3",
|
|
51
54
|
"@sveltejs/enhanced-img": "^0.11.0 || ^1.0.0",
|
|
52
55
|
"bits-ui": "^2.19.2",
|
|
53
56
|
"sv-router": "^0.19.0",
|
|
54
|
-
"svelte": "^5.
|
|
57
|
+
"svelte": "^5.57.0"
|
|
55
58
|
},
|
|
56
59
|
"devDependencies": {
|
|
57
60
|
"@eslint/compat": "^2.1.1",
|
|
58
61
|
"@eslint/js": "^10.0.1",
|
|
59
62
|
"@fontsource-variable/inter": "^5.3.0",
|
|
63
|
+
"@sjvair/sdk": "npm:@jsr/sjvair__sdk@5.0.0-beta.3",
|
|
60
64
|
"@sveltejs/enhanced-img": "^1.0.0",
|
|
61
65
|
"@sveltejs/package": "^2.5.8",
|
|
62
66
|
"@sveltejs/vite-plugin-svelte": "^7.3.0",
|
|
63
67
|
"@tailwindcss/vite": "^4.3.3",
|
|
64
68
|
"@types/node": "^26.6.0",
|
|
69
|
+
"@vitest/browser-playwright": "^5.1.0",
|
|
65
70
|
"eslint": "^10.10.0",
|
|
66
71
|
"eslint-config-prettier": "^10.1.8",
|
|
67
72
|
"eslint-plugin-svelte": "^3.23.0",
|
|
68
73
|
"globals": "^17.12.0",
|
|
74
|
+
"playwright": "^1.65.0",
|
|
69
75
|
"postcss": "^8.5.28",
|
|
70
76
|
"prettier": "^3.9.6",
|
|
71
77
|
"prettier-plugin-svelte": "^4.1.1",
|
|
@@ -78,18 +84,14 @@
|
|
|
78
84
|
"typescript": "^6.0.3",
|
|
79
85
|
"typescript-eslint": "^8.70.0",
|
|
80
86
|
"vite": "^8.3.0",
|
|
81
|
-
"vitest": "^5.0.3"
|
|
87
|
+
"vitest": "^5.0.3",
|
|
88
|
+
"vitest-browser-svelte": "^3.1.0"
|
|
82
89
|
},
|
|
83
90
|
"dependencies": {
|
|
84
91
|
"@lucide/svelte": "^1.46.0",
|
|
85
|
-
"@maptiler/sdk": "
|
|
92
|
+
"@maptiler/sdk": "~4.2.0",
|
|
86
93
|
"@maptiler/weather": "^4.0.1",
|
|
87
|
-
"@sjvair/sdk": "
|
|
88
|
-
"@tstk/builtin-extensions": "npm:@jsr/tstk__builtin-extensions@^1.0.8",
|
|
89
|
-
"@tstk/utils": "npm:@jsr/tstk__utils@^1.1.2",
|
|
90
|
-
"@turf/area": "^7.4.0",
|
|
91
|
-
"@turf/clusters-dbscan": "^7.4.0",
|
|
92
|
-
"@turf/helpers": "^7.4.0",
|
|
94
|
+
"@sjvair/map-sdk": "0.1.0-beta.6",
|
|
93
95
|
"@types/geojson": "^7946.0.16",
|
|
94
96
|
"clsx": "^2.1.1",
|
|
95
97
|
"color2k": "^2.0.4",
|
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { untrack, type ComponentProps } from "svelte";
|
|
3
|
-
import type { SJVAirEntryLevel } from "@sjvair/sdk";
|
|
4
3
|
import DataBox from "$lib/components/DataBox.svelte";
|
|
5
4
|
import LoadError from "$lib/LoadError.svelte";
|
|
6
5
|
import MonitorWidgetModal from "$lib/monitors/components/MonitorWidgetModal.svelte";
|
|
7
6
|
import MonitorSubscriptionManager from "$lib/monitors/components/MonitorSubscriptionManager.svelte";
|
|
8
7
|
import DataChart from "$lib/data-chart/DataChart.svelte";
|
|
9
|
-
import { monitorsManager } from "$lib";
|
|
8
|
+
import { monitorsManager } from "$lib/monitors/monitors.svelte.js";
|
|
10
9
|
import { MonitorDetailsState } from "$lib/monitors/monitor-details.svelte";
|
|
11
|
-
import { choosePanelPollutant } from "$lib/monitors/panel-pollutant";
|
|
10
|
+
import { choosePanelPollutant, PANEL_POLLUTANTS } from "$lib/monitors/panel-pollutant";
|
|
12
11
|
import { useMonitorMapRouter } from "./router-context";
|
|
13
12
|
import { fetchTempByCoords } from "$lib/weather";
|
|
14
13
|
import { Colors, valueToColor } from "$lib/colors";
|
|
15
|
-
import {
|
|
14
|
+
import { NEUTRAL_LEVEL_COLOR, readingDisplay, readingLevel } from "$lib/levels";
|
|
16
15
|
|
|
17
16
|
type TempDataBox = Pick<ComponentProps<typeof DataBox>, "color" | "value">;
|
|
18
17
|
|
|
@@ -28,6 +27,10 @@
|
|
|
28
27
|
// current reading for the map's pollutant.
|
|
29
28
|
const details = new MonitorDetailsState();
|
|
30
29
|
const meta = $derived(monitorsManager.meta);
|
|
30
|
+
// The map's pollutant, when it is one the panel can show.
|
|
31
|
+
const mapPollutant = $derived(
|
|
32
|
+
PANEL_POLLUTANTS.find((p) => p === monitorsManager.pollutant) ?? null
|
|
33
|
+
);
|
|
31
34
|
// Shown only once the metadata is in too; labels and levels come from it.
|
|
32
35
|
const monitor = $derived(details.status === "ready" && meta ? details.monitor : null);
|
|
33
36
|
|
|
@@ -42,14 +45,15 @@
|
|
|
42
45
|
// Bumped whenever the map's readings are replaced, i.e. its 2-minute refresh ran. The data
|
|
43
46
|
// box follows it; the chart decides for itself whether its date range can have new entries.
|
|
44
47
|
let refreshTick = $state(0);
|
|
45
|
-
let seenLatest = untrack(() => monitorsManager.latest);
|
|
48
|
+
let seenLatest = untrack(() => monitorsManager.store.latest);
|
|
46
49
|
|
|
47
50
|
$effect(() => {
|
|
48
|
-
const latest = monitorsManager.latest;
|
|
49
|
-
//
|
|
50
|
-
|
|
51
|
+
const latest = monitorsManager.store.latest;
|
|
52
|
+
// Empty before the first load and while a pollutant change is loading; wait for the
|
|
53
|
+
// readings that follow.
|
|
54
|
+
if (latest.size === 0 || latest === seenLatest) return;
|
|
51
55
|
// The first readings of a cold load arrive alongside this panel's own request.
|
|
52
|
-
const first = seenLatest ===
|
|
56
|
+
const first = seenLatest.size === 0;
|
|
53
57
|
seenLatest = latest;
|
|
54
58
|
if (first) return;
|
|
55
59
|
untrack(() => {
|
|
@@ -59,14 +63,13 @@
|
|
|
59
63
|
});
|
|
60
64
|
|
|
61
65
|
const panel = $derived(
|
|
62
|
-
monitor && meta ? choosePanelPollutant(
|
|
66
|
+
monitor && meta ? choosePanelPollutant(mapPollutant, monitor, meta) : null
|
|
63
67
|
);
|
|
64
68
|
const reading = $derived(
|
|
65
69
|
monitor && panel?.pollutant && panel.hasReading ? monitor.latest[panel.pollutant] : undefined
|
|
66
70
|
);
|
|
67
71
|
// Set when the panel shows a different pollutant than the map is coloring by.
|
|
68
72
|
const pollutantNote = $derived.by(() => {
|
|
69
|
-
const mapPollutant = monitorsManager.pollutant;
|
|
70
73
|
if (!meta || !panel?.pollutant || !panel.hasReading || !mapPollutant) return;
|
|
71
74
|
if (panel.pollutant === mapPollutant) return;
|
|
72
75
|
const shown = meta.entryType(panel.pollutant).label;
|
|
@@ -81,18 +84,15 @@
|
|
|
81
84
|
if (!monitor || !meta || !panel?.pollutant || !reading) return;
|
|
82
85
|
|
|
83
86
|
const entryType = meta.entryType(panel.pollutant);
|
|
84
|
-
const level
|
|
85
|
-
reading.value,
|
|
86
|
-
entryType.asIter.levels!
|
|
87
|
-
);
|
|
87
|
+
const level = readingLevel(reading.value, entryType.scale);
|
|
88
88
|
|
|
89
89
|
const updateDuration = meta.monitorType(monitor.type).expected_interval;
|
|
90
90
|
|
|
91
91
|
return {
|
|
92
|
-
color: level?.color ??
|
|
92
|
+
color: level?.color ?? NEUTRAL_LEVEL_COLOR,
|
|
93
93
|
header: entryType.label,
|
|
94
94
|
subheading: `(${updateDuration} avg)`,
|
|
95
|
-
value:
|
|
95
|
+
value: readingDisplay(reading.value)
|
|
96
96
|
};
|
|
97
97
|
});
|
|
98
98
|
|
|
@@ -201,9 +201,12 @@
|
|
|
201
201
|
</div>
|
|
202
202
|
{/if}
|
|
203
203
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
204
|
+
<!-- The widget shows current readings, and errors for an inactive monitor. -->
|
|
205
|
+
{#if panel.hasReading}
|
|
206
|
+
<div class="tall:block hidden">
|
|
207
|
+
<MonitorWidgetModal monitorId={monitor.id} />
|
|
208
|
+
</div>
|
|
209
|
+
{/if}
|
|
207
210
|
<!-- eslint-disable-next-line @typescript-eslint/no-explicit-any -->
|
|
208
211
|
{#if (globalThis as any).USER?.is_authenticated}
|
|
209
212
|
<MonitorSubscriptionManager monitorId={monitor.id} />
|
|
@@ -1,38 +1,44 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { Snippet } from "svelte";
|
|
3
|
-
import { onDestroy, untrack } from "svelte";
|
|
3
|
+
import { onDestroy, onMount, untrack } from "svelte";
|
|
4
4
|
import { searchParams } from "sv-router";
|
|
5
|
+
import {
|
|
6
|
+
collocationSitesPlugin,
|
|
7
|
+
hmsFirePlugin,
|
|
8
|
+
hmsSmokePlugin,
|
|
9
|
+
monitorsPlugin
|
|
10
|
+
} from "@sjvair/map-sdk";
|
|
11
|
+
import { windPlugin } from "$lib/wind/wind-plugin.js";
|
|
12
|
+
import { evPlugin } from "$lib/ev-stations/ev-plugin.svelte.js";
|
|
13
|
+
import { createEvStore } from "$lib/ev-stations/ev-store.svelte.js";
|
|
14
|
+
import { stationPopups } from "$lib/ev-stations/station-popup.svelte.js";
|
|
5
15
|
import MapShell from "$lib/map/MapShell.svelte";
|
|
16
|
+
import { createLiveMap } from "$lib/map/live-map.svelte.js";
|
|
17
|
+
import { syncPlugins } from "$lib/map/sync-plugins.svelte.js";
|
|
18
|
+
import LoadError from "$lib/LoadError.svelte";
|
|
19
|
+
import { retryMonitorsLoad } from "$lib/monitors/retry-load.js";
|
|
20
|
+
import { monitorsManager } from "$lib/monitors/monitors.svelte.js";
|
|
21
|
+
import { filteredMonitors } from "$lib/monitors/filtered-source.svelte.js";
|
|
22
|
+
import Legend from "$lib/legend/Legend.svelte";
|
|
23
|
+
import {
|
|
24
|
+
collocationSitesStore,
|
|
25
|
+
hmsFireStore,
|
|
26
|
+
hmsSmokeStore,
|
|
27
|
+
monitorsStore
|
|
28
|
+
} from "$lib/stores.svelte.js";
|
|
6
29
|
import OptionsGroup from "$lib/options/OptionsGroup.svelte";
|
|
7
30
|
import PollutantMenu from "$lib/options/menus/PollutantMenu.svelte";
|
|
8
31
|
import MonitorsMenu from "$lib/options/menus/MonitorsMenu.svelte";
|
|
9
32
|
import LayersMenu from "$lib/options/menus/LayersMenu.svelte";
|
|
10
33
|
import OverlaysMenu from "$lib/options/menus/OverlaysMenu.svelte";
|
|
11
34
|
import SettingsMenu from "$lib/options/menus/SettingsMenu.svelte";
|
|
35
|
+
import { mapOptions } from "$lib/options/map-options.svelte.js";
|
|
36
|
+
import { wantedPlugins } from "$lib/options/plugin-order";
|
|
12
37
|
import {
|
|
13
38
|
applyPollutantSelection,
|
|
14
39
|
currentSelection,
|
|
15
40
|
parsePollutantParam
|
|
16
41
|
} from "$lib/options/pollutant-param";
|
|
17
|
-
import LoadError from "$lib/LoadError.svelte";
|
|
18
|
-
import Legend from "$lib/legend/Legend.svelte";
|
|
19
|
-
import {
|
|
20
|
-
fireSmokeLegendSections,
|
|
21
|
-
pollutantLegendSection,
|
|
22
|
-
type LegendSection
|
|
23
|
-
} from "$lib/legend/legend-data";
|
|
24
|
-
import { monitorsManager } from "$lib/monitors/monitors.svelte";
|
|
25
|
-
import { monitorsMapIntegration } from "$lib/monitors/monitors-map-integration.svelte";
|
|
26
|
-
import { windMapIntegration } from "$lib/wind/wind.svelte";
|
|
27
|
-
import { baseLayerSeperator } from "$lib/map/integrations/base-layer-seperator";
|
|
28
|
-
import type { SomeMapIntegration } from "$lib/map/integrations/types";
|
|
29
|
-
import { collocationSitesManager } from "$lib/collocation-sites/collocations.svelte";
|
|
30
|
-
import { collocationSitesMapIntegration } from "$lib/collocation-sites/collocations-map-integration.svelte";
|
|
31
|
-
import { evStationsMapIntegration } from "$lib/ev-stations/ev-stations-map-integration.svelte";
|
|
32
|
-
import { hmsManager } from "$lib/hms/hms.svelte";
|
|
33
|
-
import { hmsFireMapIntegration } from "$lib/hms/hms-fire-map-integration.svelte";
|
|
34
|
-
import { FIRE_LEGEND_CATEGORIES } from "$lib/hms/hms-fire-icon-manager";
|
|
35
|
-
import { hmsSmokeMapIntegration, SMOKE_LEGEND } from "$lib/hms/hms-smoke-map-integration.svelte";
|
|
36
42
|
import Search from "$lib/search/Search.svelte";
|
|
37
43
|
import { useMonitorMapRouter } from "./router-context";
|
|
38
44
|
|
|
@@ -44,51 +50,105 @@
|
|
|
44
50
|
|
|
45
51
|
const { route, navigate, basePath } = useMonitorMapRouter();
|
|
46
52
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
collocationSitesMapIntegration,
|
|
50
|
-
windMapIntegration,
|
|
51
|
-
hmsSmokeMapIntegration,
|
|
52
|
-
hmsFireMapIntegration,
|
|
53
|
-
monitorsMapIntegration,
|
|
54
|
-
evStationsMapIntegration
|
|
55
|
-
];
|
|
53
|
+
// The map's state lives as long as this layout; MapView attaches the GL map when it mounts.
|
|
54
|
+
const map = createLiveMap();
|
|
56
55
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
// "?pollutant=none" must disable monitors before the effects below first run, so the
|
|
60
|
-
// state->URL effect never sees enabled monitors and overwrites the param.
|
|
56
|
+
// "?pollutant=none" must turn monitors off before the effects below first run, so the
|
|
57
|
+
// state->URL effect never sees monitors on and overwrites the param.
|
|
61
58
|
if (parsePollutantParam(route.search.pollutant) === "none") {
|
|
62
|
-
|
|
59
|
+
mapOptions.monitorsOn = false;
|
|
63
60
|
}
|
|
64
61
|
|
|
65
62
|
// The load screen stays up until the monitors load, so a failure has to be shown on it.
|
|
66
|
-
|
|
63
|
+
const loadFailed = $derived(monitorsStore.dataSource.status.state === "error");
|
|
67
64
|
|
|
68
65
|
function loadMonitors() {
|
|
69
|
-
loadFailed = false;
|
|
70
66
|
// A rejected init() can be retried: a later call starts a fresh run.
|
|
71
|
-
monitorsManager.init(route.search.pollutant).catch((error) =>
|
|
72
|
-
console.error(error);
|
|
73
|
-
loadFailed = true;
|
|
74
|
-
});
|
|
67
|
+
monitorsManager.init(route.search.pollutant).catch((error) => console.error(error));
|
|
75
68
|
}
|
|
76
69
|
|
|
77
70
|
loadMonitors();
|
|
78
|
-
// A previous instance of this layout stopped polling in onDestroy; init() is a no-op by now.
|
|
79
|
-
monitorsManager.resumeAutoUpdate();
|
|
80
|
-
collocationSitesManager.init();
|
|
81
|
-
hmsManager.init();
|
|
82
|
-
monitorsMapIntegration.onMonitorClick = (id: string) => {
|
|
83
|
-
navigate(`${basePath}/monitor/:id`, { params: { id } }).catch(console.error);
|
|
84
|
-
};
|
|
85
71
|
|
|
86
|
-
|
|
72
|
+
const panelOpen = $derived(route.pathname.startsWith(`${basePath}/monitor/`));
|
|
73
|
+
const selectedId = $derived(panelOpen ? route.params.id : undefined);
|
|
74
|
+
|
|
75
|
+
// The monitors the Monitors menu's filters let through; clusters are built from these.
|
|
76
|
+
const monitorsSource = filteredMonitors(monitorsStore.dataSource, mapOptions);
|
|
77
|
+
|
|
78
|
+
// EV chargers: each level is fetched the first time it is wanted. A failure shows in the legend
|
|
79
|
+
// with a retry.
|
|
80
|
+
const evStore = createEvStore();
|
|
81
|
+
const evPopups = stationPopups(map);
|
|
82
|
+
$effect(() => {
|
|
83
|
+
const wanted = wantedPlugins(mapOptions, monitorsManager.pollutant);
|
|
84
|
+
for (const [key, level] of [
|
|
85
|
+
["ev-l2", "lvl2"],
|
|
86
|
+
["ev-l3", "lvl3"]
|
|
87
|
+
] as const) {
|
|
88
|
+
if (wanted.has(key)) untrack(() => evStore.load(level)).catch(console.error);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// The options decide which plugins are on the map, in DRAW_ORDER. `clustered` is fixed when a
|
|
93
|
+
// plugin is built, so a change rebuilds it.
|
|
94
|
+
syncPlugins(
|
|
95
|
+
map,
|
|
96
|
+
() => wantedPlugins(mapOptions, monitorsManager.pollutant),
|
|
97
|
+
{
|
|
98
|
+
monitors: () => ({
|
|
99
|
+
plugin: monitorsPlugin({
|
|
100
|
+
id: "monitors",
|
|
101
|
+
source: monitorsSource,
|
|
102
|
+
layerLabel: "Monitors",
|
|
103
|
+
selectable: true,
|
|
104
|
+
selectedStyle: "enlarge",
|
|
105
|
+
clustered: mapOptions.clustered
|
|
106
|
+
}),
|
|
107
|
+
// A removed plugin forgets its mode and selection, so each add passes them again.
|
|
108
|
+
options: {
|
|
109
|
+
selectionMode: "single",
|
|
110
|
+
selection: selectedId ? { kind: "monitors", ids: [selectedId] } : null
|
|
111
|
+
}
|
|
112
|
+
}),
|
|
113
|
+
"ev-l2": () =>
|
|
114
|
+
evPlugin("lvl2", evStore.dataSource("lvl2"), { clustered: mapOptions.clustered }),
|
|
115
|
+
"ev-l3": () =>
|
|
116
|
+
evPlugin("lvl3", evStore.dataSource("lvl3"), { clustered: mapOptions.clustered })
|
|
117
|
+
},
|
|
118
|
+
() => mapOptions.clustered
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
// Wind, Fire & Smoke and collocation sites take no build-time option, so they sit in their own call
|
|
122
|
+
// without `deps`: a Marker Clusters change rebuilds only the monitors above. DRAW_ORDER still
|
|
123
|
+
// holds across the two calls because `syncPlugins` reads `map.plugins` (every plugin on the
|
|
124
|
+
// map, whichever call added it) to find the plugin to add before.
|
|
125
|
+
syncPlugins(map, () => wantedPlugins(mapOptions, monitorsManager.pollutant), {
|
|
126
|
+
wind: () => windPlugin(),
|
|
127
|
+
"hms-smoke": () =>
|
|
128
|
+
hmsSmokePlugin({
|
|
129
|
+
id: "hms-smoke",
|
|
130
|
+
source: hmsSmokeStore.dataSource,
|
|
131
|
+
layerLabel: "Smoke"
|
|
132
|
+
}),
|
|
133
|
+
"hms-fire": () =>
|
|
134
|
+
hmsFirePlugin({
|
|
135
|
+
id: "hms-fire",
|
|
136
|
+
source: hmsFireStore.groups,
|
|
137
|
+
hotspots: hmsFireStore.hotspots,
|
|
138
|
+
layerLabel: "Fires"
|
|
139
|
+
}),
|
|
140
|
+
"collocation-sites": () =>
|
|
141
|
+
collocationSitesPlugin({
|
|
142
|
+
id: "collocation-sites",
|
|
143
|
+
source: collocationSitesStore.dataSource,
|
|
144
|
+
layerLabel: "Collocation sites"
|
|
145
|
+
})
|
|
146
|
+
});
|
|
87
147
|
|
|
88
148
|
// Keep the Pollutant menu's selection (pm25 / o3 / none) and the "pollutant" URL param in
|
|
89
|
-
// sync, both directions. init() only seeds the pollutant on the
|
|
149
|
+
// sync, both directions. init() only seeds the pollutant on the store's first-ever
|
|
90
150
|
// initialization; this effect applies a later "?pollutant=" change (e.g. navigating in from
|
|
91
|
-
// elsewhere) to an already-running map.
|
|
151
|
+
// elsewhere, or back/forward) to an already-running map.
|
|
92
152
|
$effect(() => {
|
|
93
153
|
const selection = parsePollutantParam(route.search.pollutant);
|
|
94
154
|
if (!monitorsManager.initialized || !selection) return;
|
|
@@ -96,47 +156,54 @@
|
|
|
96
156
|
// state here would make a UI-driven change re-trigger this effect before the effect below
|
|
97
157
|
// syncs the URL, so it would see the stale URL and revert the user's change.
|
|
98
158
|
untrack(() => {
|
|
99
|
-
if (
|
|
100
|
-
|
|
101
|
-
) {
|
|
102
|
-
applyPollutantSelection(selection, pollutantTargets);
|
|
159
|
+
if (currentSelection(monitorsManager.pollutant, mapOptions.monitorsOn) !== selection) {
|
|
160
|
+
applyPollutantSelection(selection, { manager: monitorsManager, options: mapOptions });
|
|
103
161
|
}
|
|
104
162
|
});
|
|
105
163
|
});
|
|
106
164
|
|
|
107
165
|
// ...and the reverse: reflect UI-driven changes back to the URL.
|
|
108
166
|
$effect(() => {
|
|
109
|
-
const selection = currentSelection(monitorsManager.pollutant,
|
|
167
|
+
const selection = currentSelection(monitorsManager.pollutant, mapOptions.monitorsOn);
|
|
110
168
|
if (selection && searchParams.get("pollutant") !== selection) {
|
|
111
169
|
searchParams.set("pollutant", selection);
|
|
112
170
|
}
|
|
113
171
|
});
|
|
114
172
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
173
|
+
onMount(() => {
|
|
174
|
+
// A failed load leaves its layer empty (map-sdk's legend shows the status); log it once.
|
|
175
|
+
for (const store of [hmsSmokeStore, hmsFireStore, collocationSitesStore]) {
|
|
176
|
+
store.init().catch(console.error);
|
|
177
|
+
}
|
|
178
|
+
// A previous instance of this layout paused polling in onDestroy; init() is a no-op by now.
|
|
179
|
+
monitorsManager.resumeAutoUpdate();
|
|
180
|
+
return map.on("featureclick", ({ pluginId, ref, lngLat }) => {
|
|
181
|
+
// A station clicked on the map or picked from the list: its popup.
|
|
182
|
+
if ((pluginId === "ev-l2" || pluginId === "ev-l3") && ref.kind === "feature") {
|
|
183
|
+
const station = evStore.station(ref.id);
|
|
184
|
+
if (station) evPopups.open(pluginId, station, lngLat);
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
if (pluginId !== "monitors" || ref.kind !== "monitor") return;
|
|
188
|
+
navigate(`${basePath}/monitor/:id`, { params: { id: ref.id } }).catch(console.error);
|
|
189
|
+
map.easeTo({ center: lngLat, minZoom: 12 });
|
|
190
|
+
});
|
|
119
191
|
});
|
|
120
192
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
if (pollutant) sections.push(pollutant);
|
|
126
|
-
}
|
|
127
|
-
if (hmsFireMapIntegration.enabled && hmsSmokeMapIntegration.enabled) {
|
|
128
|
-
sections.push(...fireSmokeLegendSections(FIRE_LEGEND_CATEGORIES, SMOKE_LEGEND));
|
|
129
|
-
}
|
|
130
|
-
return sections;
|
|
193
|
+
// The selected monitor follows the route: the open panel's monitor, none once it closes.
|
|
194
|
+
$effect(() => {
|
|
195
|
+
const id = selectedId;
|
|
196
|
+
map.setSelection("monitors", id ? { kind: "monitors", ids: [id] } : null);
|
|
131
197
|
});
|
|
132
198
|
|
|
133
199
|
onDestroy(() => {
|
|
134
|
-
monitorsManager.
|
|
200
|
+
monitorsManager.pauseAutoUpdate();
|
|
201
|
+
map.destroy();
|
|
135
202
|
});
|
|
136
203
|
</script>
|
|
137
204
|
|
|
138
205
|
<MapShell
|
|
139
|
-
{
|
|
206
|
+
{map}
|
|
140
207
|
ready={monitorsManager.initialized}
|
|
141
208
|
{panelOpen}
|
|
142
209
|
knownRoutes={[`${basePath}/monitor/`]}
|
|
@@ -144,7 +211,10 @@
|
|
|
144
211
|
>
|
|
145
212
|
{#snippet loadError()}
|
|
146
213
|
{#if loadFailed}
|
|
147
|
-
<LoadError
|
|
214
|
+
<LoadError
|
|
215
|
+
message="Couldn't load air monitors."
|
|
216
|
+
onretry={() => retryMonitorsLoad(monitorsStore, loadMonitors)}
|
|
217
|
+
/>
|
|
148
218
|
{/if}
|
|
149
219
|
{/snippet}
|
|
150
220
|
{#snippet menu()}
|
|
@@ -155,16 +225,16 @@
|
|
|
155
225
|
<OptionsGroup>
|
|
156
226
|
<LayersMenu />
|
|
157
227
|
<OverlaysMenu />
|
|
158
|
-
<SettingsMenu />
|
|
228
|
+
<SettingsMenu {map} />
|
|
159
229
|
</OptionsGroup>
|
|
160
230
|
{/snippet}
|
|
161
|
-
{#snippet search()}
|
|
162
|
-
<Search />
|
|
163
|
-
{/snippet}
|
|
164
231
|
{#snippet overlays()}
|
|
165
232
|
<div class="pointer-events-none absolute bottom-4 left-4 z-10">
|
|
166
|
-
<Legend
|
|
233
|
+
<Legend {map} />
|
|
167
234
|
</div>
|
|
168
235
|
{/snippet}
|
|
236
|
+
{#snippet search()}
|
|
237
|
+
<Search {map} />
|
|
238
|
+
{/snippet}
|
|
169
239
|
{@render children()}
|
|
170
240
|
</MapShell>
|
package/src/lib/abort.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
export type SignalOptions = { signal?: AbortSignal };
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A signal that aborts as soon as any input does, with that input's reason. Already aborted if
|
|
5
|
+
* any input is. Undefined inputs are ignored. Call `dispose()` to drop the listeners.
|
|
6
|
+
*/
|
|
7
|
+
export function anySignal(...signals: Array<AbortSignal | undefined>): {
|
|
8
|
+
signal: AbortSignal;
|
|
9
|
+
dispose(): void;
|
|
10
|
+
} {
|
|
11
|
+
const controller = new AbortController();
|
|
12
|
+
const cleanups: Array<() => void> = [];
|
|
13
|
+
const dispose = () => {
|
|
14
|
+
for (const cleanup of cleanups.splice(0)) cleanup();
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
for (const input of signals) {
|
|
18
|
+
if (!input) continue;
|
|
19
|
+
if (input.aborted) {
|
|
20
|
+
controller.abort(input.reason);
|
|
21
|
+
dispose();
|
|
22
|
+
break;
|
|
23
|
+
}
|
|
24
|
+
const onAbort = () => {
|
|
25
|
+
controller.abort(input.reason);
|
|
26
|
+
dispose();
|
|
27
|
+
};
|
|
28
|
+
input.addEventListener("abort", onAbort, { once: true });
|
|
29
|
+
cleanups.push(() => input.removeEventListener("abort", onAbort));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return { signal: controller.signal, dispose };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Settles like `promise`, unless `signal` aborts first, in which case it rejects with
|
|
37
|
+
* `signal.reason`. The inner promise's later outcome is swallowed.
|
|
38
|
+
*/
|
|
39
|
+
export function untilAborted<T>(promise: Promise<T>, signal: AbortSignal | undefined): Promise<T> {
|
|
40
|
+
if (!signal) return promise;
|
|
41
|
+
if (signal.aborted) {
|
|
42
|
+
promise.catch(() => {});
|
|
43
|
+
return Promise.reject(signal.reason);
|
|
44
|
+
}
|
|
45
|
+
return new Promise<T>((resolve, reject) => {
|
|
46
|
+
const onAbort = () => reject(signal.reason);
|
|
47
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
48
|
+
promise.then(
|
|
49
|
+
(value) => {
|
|
50
|
+
signal.removeEventListener("abort", onAbort);
|
|
51
|
+
resolve(value);
|
|
52
|
+
},
|
|
53
|
+
(error) => {
|
|
54
|
+
signal.removeEventListener("abort", onAbort);
|
|
55
|
+
reject(error);
|
|
56
|
+
}
|
|
57
|
+
);
|
|
58
|
+
});
|
|
59
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { darken, readableColor } from "color2k";
|
|
3
|
+
import { NEUTRAL_LEVEL_COLOR } from "$lib/levels";
|
|
3
4
|
|
|
4
5
|
interface DataBoxProps {
|
|
5
6
|
color: string;
|
|
@@ -8,7 +9,9 @@
|
|
|
8
9
|
value: string;
|
|
9
10
|
}
|
|
10
11
|
|
|
11
|
-
const { color, header, subheading, value }: DataBoxProps = $props();
|
|
12
|
+
const { color: givenColor, header, subheading, value }: DataBoxProps = $props();
|
|
13
|
+
// An empty color (a level without one) would make color2k throw.
|
|
14
|
+
const color = $derived(givenColor || NEUTRAL_LEVEL_COLOR);
|
|
12
15
|
const borderColor = $derived(darken(color, 0.1));
|
|
13
16
|
const textColor = $derived(readableColor(color));
|
|
14
17
|
</script>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface MonitorMapConfig {
|
|
2
|
+
maptilerKey: string;
|
|
3
|
+
nrelKey: string;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
let current: MonitorMapConfig | null = null;
|
|
7
|
+
|
|
8
|
+
/** Hosts call this once at startup, before the map or the EV layer is used. */
|
|
9
|
+
export function configureMonitorMap(config: MonitorMapConfig): void {
|
|
10
|
+
current = { ...config };
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function monitorMapConfig(): MonitorMapConfig {
|
|
14
|
+
if (!current) throw new Error("monitor-map: call configureMonitorMap() first");
|
|
15
|
+
return current;
|
|
16
|
+
}
|
package/src/lib/core.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Light entry (`@sjvair/monitor-map/core`): what a host app needs at startup, without the map.
|
|
3
3
|
*
|
|
4
|
-
* Nothing imported here may reach @maptiler/sdk, @maptiler/weather or uplot at runtime;
|
|
4
|
+
* Nothing imported here may reach @maptiler/sdk, maplibre, @maptiler/weather or uplot at runtime;
|
|
5
5
|
* `npm run test:bundle` enforces it. Everything is re-exported from the same modules the
|
|
6
6
|
* main entry uses, so `monitorsManager` and the router context stay single instances
|
|
7
7
|
* whichever entries a host mixes.
|
|
8
8
|
*/
|
|
9
|
+
export { configureMonitorMap, monitorMapConfig, type MonitorMapConfig } from "./config.svelte.js";
|
|
9
10
|
export {
|
|
10
11
|
ROUTER_KEY,
|
|
11
12
|
provideMonitorMapRouter,
|
|
@@ -14,4 +15,3 @@ export {
|
|
|
14
15
|
} from "./router-context";
|
|
15
16
|
export { monitorsManager } from "./monitors/monitors.svelte.js";
|
|
16
17
|
export type { MonitorsManager, MonitorsInitOptions } from "./monitors/monitors.svelte.js";
|
|
17
|
-
export type { MonitorsDataSource, MonitorsPollutant } from "./monitors/types";
|