@sjvair/monitor-map 3.2.2 → 3.3.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/lib/MonitorMapLayout.svelte +27 -90
- package/dist/lib/index.d.ts +1 -0
- package/dist/lib/index.js +1 -0
- package/dist/lib/map/MapShell.svelte +150 -0
- package/dist/lib/map/MapShell.svelte.d.ts +48 -0
- package/dist/lib/map/map.svelte.js +1 -1
- package/package.json +1 -1
- package/src/lib/MonitorMapLayout.svelte +27 -90
- package/src/lib/index.ts +1 -0
- package/src/lib/map/MapShell.svelte +150 -0
- package/src/lib/map/map.svelte.ts +1 -1
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { Snippet } from "svelte";
|
|
3
|
-
import { onDestroy } from "svelte";
|
|
4
|
-
import
|
|
5
|
-
import Map from "./map/Map.svelte";
|
|
6
|
-
import Menu from "./map/Menu.svelte";
|
|
3
|
+
import { onDestroy, untrack } from "svelte";
|
|
4
|
+
import MapShell from "./map/MapShell.svelte";
|
|
7
5
|
import MonitorsDisplayOptions from "./monitors/components/MonitorsDisplayOptions.svelte";
|
|
8
6
|
import MapLayersDisplayOptions from "./components/MapLayersDisplayOptions.svelte";
|
|
9
7
|
import MapStyleDisplayOptions from "./map/MapStyleDisplayOptions.svelte";
|
|
10
|
-
import { mapManager } from "./map/map.svelte";
|
|
11
8
|
import { monitorsManager } from "./monitors/monitors.svelte";
|
|
12
9
|
import { monitorsMapIntegration } from "./monitors/monitors-map-integration.svelte";
|
|
13
10
|
import { windMapIntegration } from "./wind/wind.svelte";
|
|
@@ -33,8 +30,6 @@
|
|
|
33
30
|
|
|
34
31
|
const { route, navigate, basePath } = useMonitorMapRouter();
|
|
35
32
|
|
|
36
|
-
const TRANSITION_MS = 300;
|
|
37
|
-
|
|
38
33
|
const integrations: Array<SomeMapIntegration> = [
|
|
39
34
|
baseLayerSeperator,
|
|
40
35
|
collocationSitesMapIntegration,
|
|
@@ -59,10 +54,15 @@
|
|
|
59
54
|
// applies a later "?pollutant=" change (e.g. navigating in from elsewhere) to an already-running manager.
|
|
60
55
|
$effect(() => {
|
|
61
56
|
const urlPollutant = route.search.pollutant;
|
|
57
|
+
// Read monitorsManager.pollutant via untrack: this effect must only react to the URL
|
|
58
|
+
// changing (e.g. navigating in from elsewhere), not to monitorsManager.pollutant itself.
|
|
59
|
+
// Tracking it here would make a UI-driven pollutant change (e.g. the display-options
|
|
60
|
+
// toggle) re-trigger this effect before the manager->URL effect below can sync the URL,
|
|
61
|
+
// so this effect would see the still-stale URL and immediately revert the user's change.
|
|
62
62
|
if (
|
|
63
63
|
monitorsManager.initialized &&
|
|
64
64
|
(urlPollutant === "pm25" || urlPollutant === "o3") &&
|
|
65
|
-
monitorsManager.pollutant !== urlPollutant
|
|
65
|
+
untrack(() => monitorsManager.pollutant) !== urlPollutant
|
|
66
66
|
) {
|
|
67
67
|
monitorsManager.pollutant = urlPollutant;
|
|
68
68
|
}
|
|
@@ -76,22 +76,6 @@
|
|
|
76
76
|
}
|
|
77
77
|
});
|
|
78
78
|
|
|
79
|
-
$effect(() => {
|
|
80
|
-
if (mapManager.map && monitorsManager.initialized) {
|
|
81
|
-
mapManager.map.once("idle", () => disableLoadScreen());
|
|
82
|
-
}
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
$effect(() => {
|
|
86
|
-
if (panelOpen) {
|
|
87
|
-
// Container width snapped to open; resize after layout settles
|
|
88
|
-
requestAnimationFrame(() => mapManager.map?.resize());
|
|
89
|
-
} else {
|
|
90
|
-
// Container width snaps back after the transform transition ends
|
|
91
|
-
setTimeout(() => mapManager.map?.resize(), TRANSITION_MS);
|
|
92
|
-
}
|
|
93
|
-
});
|
|
94
|
-
|
|
95
79
|
// Clear selected icon scale when the detail panel closes
|
|
96
80
|
$effect(() => {
|
|
97
81
|
if (panelOpen) return;
|
|
@@ -101,75 +85,28 @@
|
|
|
101
85
|
onDestroy(() => {
|
|
102
86
|
monitorsManager.autoUpdate.stop();
|
|
103
87
|
});
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* HACK: Fix for escaping sv-router and allowing navigation to other pages,
|
|
107
|
-
* as well as navigating back
|
|
108
|
-
*/
|
|
109
|
-
const rootPath = basePath || "/";
|
|
110
|
-
const knownRoutes = [`${basePath}/monitor/`];
|
|
111
|
-
|
|
112
|
-
window.addEventListener("pageshow", (e) => {
|
|
113
|
-
if (e.persisted) window.location.reload();
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
document.addEventListener(
|
|
117
|
-
"click",
|
|
118
|
-
(e) => {
|
|
119
|
-
const anchor = e
|
|
120
|
-
.composedPath()
|
|
121
|
-
.find((el) => el instanceof HTMLAnchorElement) as HTMLAnchorElement;
|
|
122
|
-
if (!anchor) return;
|
|
123
|
-
const { pathname } = new URL(anchor.href);
|
|
124
|
-
const isKnown = pathname === rootPath || knownRoutes.some((r) => pathname.startsWith(r));
|
|
125
|
-
if (!isKnown) {
|
|
126
|
-
e.stopImmediatePropagation();
|
|
127
|
-
window.location.href = anchor.href;
|
|
128
|
-
}
|
|
129
|
-
},
|
|
130
|
-
{ capture: true }
|
|
131
|
-
);
|
|
132
88
|
</script>
|
|
133
89
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
90
|
+
<MapShell
|
|
91
|
+
{integrations}
|
|
92
|
+
ready={monitorsManager.initialized}
|
|
93
|
+
{panelOpen}
|
|
94
|
+
knownRoutes={[`${basePath}/monitor/`]}
|
|
95
|
+
{basePath}
|
|
96
|
+
>
|
|
97
|
+
{#snippet menu()}
|
|
98
|
+
<MonitorsDisplayOptions />
|
|
99
|
+
<EvStationsDisplayOptions />
|
|
100
|
+
<MapLayersDisplayOptions />
|
|
101
|
+
<MapStyleDisplayOptions />
|
|
102
|
+
{/snippet}
|
|
103
|
+
{#snippet overlays()}
|
|
141
104
|
<div class="pointer-events-none absolute bottom-0 left-0 z-10">
|
|
142
105
|
<MapLegend />
|
|
143
106
|
</div>
|
|
144
|
-
<div class="absolute top-4 left-
|
|
145
|
-
<
|
|
146
|
-
<Menu>
|
|
147
|
-
<MonitorsDisplayOptions />
|
|
148
|
-
<EvStationsDisplayOptions />
|
|
149
|
-
<MapLayersDisplayOptions />
|
|
150
|
-
<MapStyleDisplayOptions />
|
|
151
|
-
</Menu>
|
|
152
|
-
</div>
|
|
153
|
-
<div class="absolute left-16">
|
|
154
|
-
<Search />
|
|
155
|
-
</div>
|
|
156
|
-
</div>
|
|
157
|
-
</div>
|
|
158
|
-
<div
|
|
159
|
-
class={[
|
|
160
|
-
"panel-containr w-full shrink-0 overflow-hidden",
|
|
161
|
-
panelOpen ? "h-1/2 md:h-full md:w-1/3" : "md:w-0"
|
|
162
|
-
]}
|
|
163
|
-
>
|
|
164
|
-
<div
|
|
165
|
-
class={[
|
|
166
|
-
"panel-contet h-full duration-300 ease-in-out",
|
|
167
|
-
panelOpen
|
|
168
|
-
? "translate-y-0 md:translate-x-0"
|
|
169
|
-
: "translate-y-full md:translate-x-full md:translate-y-0"
|
|
170
|
-
]}
|
|
171
|
-
>
|
|
172
|
-
{@render children()}
|
|
107
|
+
<div class="absolute top-4 left-20 z-10">
|
|
108
|
+
<Search />
|
|
173
109
|
</div>
|
|
174
|
-
|
|
175
|
-
|
|
110
|
+
{/snippet}
|
|
111
|
+
{@render children()}
|
|
112
|
+
</MapShell>
|
package/dist/lib/index.d.ts
CHANGED
|
@@ -44,6 +44,7 @@ export { baseLayerSeperator } from "./map/integrations/base-layer-seperator";
|
|
|
44
44
|
export { hmsFireMapIntegration } from "./hms/hms-fire-map-integration.svelte";
|
|
45
45
|
export { hmsSmokeMapIntegration } from "./hms/hms-smoke-map-integration.svelte";
|
|
46
46
|
export { default as Map } from "./map/Map.svelte";
|
|
47
|
+
export { default as MapShell, type MapShellProps } from "./map/MapShell.svelte";
|
|
47
48
|
export { default as Menu } from "./map/Menu.svelte";
|
|
48
49
|
export { default as LoadScreen, loadScreenState, enable as enableLoadScreen, disable as disableLoadScreen } from "./LoadScreen.svelte";
|
|
49
50
|
export type { LoadScreenState } from "./LoadScreen.svelte";
|
package/dist/lib/index.js
CHANGED
|
@@ -42,6 +42,7 @@ export { hmsFireMapIntegration } from "./hms/hms-fire-map-integration.svelte";
|
|
|
42
42
|
export { hmsSmokeMapIntegration } from "./hms/hms-smoke-map-integration.svelte";
|
|
43
43
|
// Components
|
|
44
44
|
export { default as Map } from "./map/Map.svelte";
|
|
45
|
+
export { default as MapShell } from "./map/MapShell.svelte";
|
|
45
46
|
export { default as Menu } from "./map/Menu.svelte";
|
|
46
47
|
export { default as LoadScreen, loadScreenState, enable as enableLoadScreen, disable as disableLoadScreen } from "./LoadScreen.svelte";
|
|
47
48
|
export { default as MonitorsDisplayOptions } from "./monitors/components/MonitorsDisplayOptions.svelte";
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
import type { Snippet } from "svelte";
|
|
3
|
+
import type { SomeMapIntegration } from "./integrations/types";
|
|
4
|
+
|
|
5
|
+
export interface MapShellProps {
|
|
6
|
+
/** Map integrations to register, e.g. [monitorsMapIntegration] */
|
|
7
|
+
integrations: Array<SomeMapIntegration>;
|
|
8
|
+
/**
|
|
9
|
+
* True once the caller's own data managers have finished their initial
|
|
10
|
+
* load. The shell hides the load screen once the map is idle AND this
|
|
11
|
+
* is true.
|
|
12
|
+
*/
|
|
13
|
+
ready: boolean;
|
|
14
|
+
/** True when the routed detail panel (`children`) should be visible */
|
|
15
|
+
panelOpen: boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Path prefixes that are part of this map's own routed panel (e.g.
|
|
18
|
+
* [`${basePath}/monitor/`]) — internal navigation to these should not
|
|
19
|
+
* trigger the escape-hatch full-page navigation below.
|
|
20
|
+
*/
|
|
21
|
+
knownRoutes?: Array<string>;
|
|
22
|
+
/** Rendered inside the display-options menu toggle */
|
|
23
|
+
menu?: Snippet;
|
|
24
|
+
/** Freeform absolutely-positioned content over the map (legend, search, etc.) */
|
|
25
|
+
overlays?: Snippet;
|
|
26
|
+
/** The routed detail panel content */
|
|
27
|
+
children?: Snippet;
|
|
28
|
+
/**
|
|
29
|
+
* Base path to use for the sv-router escape-hatch's "known" root route.
|
|
30
|
+
* If provided, used directly instead of reading `basePath` from
|
|
31
|
+
* `useMonitorMapRouter()`. Only relevant when `routerEscapeHatch` is
|
|
32
|
+
* enabled.
|
|
33
|
+
*/
|
|
34
|
+
basePath?: string;
|
|
35
|
+
/**
|
|
36
|
+
* Whether to install the sv-router escape-hatch (`pageshow`/`click`
|
|
37
|
+
* listeners that force a full-page navigation for links outside this
|
|
38
|
+
* map's own routes). Defaults to `true`, preserving the existing
|
|
39
|
+
* full-page-map behavior. When `true` and no `basePath` prop is given,
|
|
40
|
+
* `MapShell` falls back to reading `basePath` from
|
|
41
|
+
* `useMonitorMapRouter()`. Set to `false` to embed `MapShell` inside a
|
|
42
|
+
* host app with its own routing — this also skips calling
|
|
43
|
+
* `useMonitorMapRouter()` entirely, so no `provideMonitorMapRouter(...)`
|
|
44
|
+
* ancestor is required.
|
|
45
|
+
*/
|
|
46
|
+
routerEscapeHatch?: boolean;
|
|
47
|
+
}
|
|
48
|
+
</script>
|
|
49
|
+
|
|
50
|
+
<script lang="ts">
|
|
51
|
+
import LoadScreen, { disable as disableLoadScreen } from "../LoadScreen.svelte";
|
|
52
|
+
import Map from "./Map.svelte";
|
|
53
|
+
import Menu from "./Menu.svelte";
|
|
54
|
+
import { mapManager } from "./map.svelte";
|
|
55
|
+
import { useMonitorMapRouter } from "../router-context";
|
|
56
|
+
|
|
57
|
+
let {
|
|
58
|
+
integrations,
|
|
59
|
+
ready,
|
|
60
|
+
panelOpen,
|
|
61
|
+
knownRoutes = [],
|
|
62
|
+
menu,
|
|
63
|
+
overlays,
|
|
64
|
+
children,
|
|
65
|
+
basePath: providedBasePath,
|
|
66
|
+
routerEscapeHatch = true
|
|
67
|
+
}: MapShellProps = $props();
|
|
68
|
+
|
|
69
|
+
const basePath = providedBasePath ?? (routerEscapeHatch ? useMonitorMapRouter().basePath : "");
|
|
70
|
+
const TRANSITION_MS = 300;
|
|
71
|
+
|
|
72
|
+
$effect(() => {
|
|
73
|
+
// mapManager.map is only ever assigned once its own initial "load" has
|
|
74
|
+
// already fired (see initializeMap in map.svelte.ts), so by the time this
|
|
75
|
+
// effect sees a non-null map, it's already safe to reveal — waiting for a
|
|
76
|
+
// further "idle" event here is unreliable since one may never fire again.
|
|
77
|
+
if (mapManager.map && ready) {
|
|
78
|
+
disableLoadScreen();
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
$effect(() => {
|
|
83
|
+
if (panelOpen) {
|
|
84
|
+
// Container width snapped to open; resize after layout settles
|
|
85
|
+
requestAnimationFrame(() => mapManager.map?.resize());
|
|
86
|
+
} else {
|
|
87
|
+
// Container width snaps back after the transform transition ends
|
|
88
|
+
setTimeout(() => mapManager.map?.resize(), TRANSITION_MS);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* HACK: Fix for escaping sv-router and allowing navigation to other pages,
|
|
94
|
+
* as well as navigating back
|
|
95
|
+
*/
|
|
96
|
+
if (routerEscapeHatch) {
|
|
97
|
+
const rootPath = basePath || "/";
|
|
98
|
+
|
|
99
|
+
window.addEventListener("pageshow", (e) => {
|
|
100
|
+
if (e.persisted) window.location.reload();
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
document.addEventListener(
|
|
104
|
+
"click",
|
|
105
|
+
(e) => {
|
|
106
|
+
const anchor = e
|
|
107
|
+
.composedPath()
|
|
108
|
+
.find((el) => el instanceof HTMLAnchorElement) as HTMLAnchorElement;
|
|
109
|
+
if (!anchor) return;
|
|
110
|
+
const { pathname } = new URL(anchor.href);
|
|
111
|
+
const isKnown = pathname === rootPath || knownRoutes.some((r) => pathname.startsWith(r));
|
|
112
|
+
if (!isKnown) {
|
|
113
|
+
e.stopImmediatePropagation();
|
|
114
|
+
window.location.href = anchor.href;
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
{ capture: true }
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
</script>
|
|
121
|
+
|
|
122
|
+
<div class="relative flex h-full w-full flex-col md:flex-row">
|
|
123
|
+
<LoadScreen />
|
|
124
|
+
<div class="relative flex-1 overflow-hidden">
|
|
125
|
+
<Map {integrations} />
|
|
126
|
+
{@render overlays?.()}
|
|
127
|
+
{#if menu}
|
|
128
|
+
<div class="absolute top-4 left-4 z-10">
|
|
129
|
+
<Menu>{@render menu()}</Menu>
|
|
130
|
+
</div>
|
|
131
|
+
{/if}
|
|
132
|
+
</div>
|
|
133
|
+
<div
|
|
134
|
+
class={[
|
|
135
|
+
"panel-containr w-full shrink-0 overflow-hidden",
|
|
136
|
+
panelOpen ? "h-1/2 md:h-full md:w-1/3" : "md:w-0"
|
|
137
|
+
]}
|
|
138
|
+
>
|
|
139
|
+
<div
|
|
140
|
+
class={[
|
|
141
|
+
"panel-contet h-full duration-300 ease-in-out",
|
|
142
|
+
panelOpen
|
|
143
|
+
? "translate-y-0 md:translate-x-0"
|
|
144
|
+
: "translate-y-full md:translate-x-full md:translate-y-0"
|
|
145
|
+
]}
|
|
146
|
+
>
|
|
147
|
+
{@render children?.()}
|
|
148
|
+
</div>
|
|
149
|
+
</div>
|
|
150
|
+
</div>
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { Snippet } from "svelte";
|
|
2
|
+
import type { SomeMapIntegration } from "./integrations/types";
|
|
3
|
+
export interface MapShellProps {
|
|
4
|
+
/** Map integrations to register, e.g. [monitorsMapIntegration] */
|
|
5
|
+
integrations: Array<SomeMapIntegration>;
|
|
6
|
+
/**
|
|
7
|
+
* True once the caller's own data managers have finished their initial
|
|
8
|
+
* load. The shell hides the load screen once the map is idle AND this
|
|
9
|
+
* is true.
|
|
10
|
+
*/
|
|
11
|
+
ready: boolean;
|
|
12
|
+
/** True when the routed detail panel (`children`) should be visible */
|
|
13
|
+
panelOpen: boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Path prefixes that are part of this map's own routed panel (e.g.
|
|
16
|
+
* [`${basePath}/monitor/`]) — internal navigation to these should not
|
|
17
|
+
* trigger the escape-hatch full-page navigation below.
|
|
18
|
+
*/
|
|
19
|
+
knownRoutes?: Array<string>;
|
|
20
|
+
/** Rendered inside the display-options menu toggle */
|
|
21
|
+
menu?: Snippet;
|
|
22
|
+
/** Freeform absolutely-positioned content over the map (legend, search, etc.) */
|
|
23
|
+
overlays?: Snippet;
|
|
24
|
+
/** The routed detail panel content */
|
|
25
|
+
children?: Snippet;
|
|
26
|
+
/**
|
|
27
|
+
* Base path to use for the sv-router escape-hatch's "known" root route.
|
|
28
|
+
* If provided, used directly instead of reading `basePath` from
|
|
29
|
+
* `useMonitorMapRouter()`. Only relevant when `routerEscapeHatch` is
|
|
30
|
+
* enabled.
|
|
31
|
+
*/
|
|
32
|
+
basePath?: string;
|
|
33
|
+
/**
|
|
34
|
+
* Whether to install the sv-router escape-hatch (`pageshow`/`click`
|
|
35
|
+
* listeners that force a full-page navigation for links outside this
|
|
36
|
+
* map's own routes). Defaults to `true`, preserving the existing
|
|
37
|
+
* full-page-map behavior. When `true` and no `basePath` prop is given,
|
|
38
|
+
* `MapShell` falls back to reading `basePath` from
|
|
39
|
+
* `useMonitorMapRouter()`. Set to `false` to embed `MapShell` inside a
|
|
40
|
+
* host app with its own routing — this also skips calling
|
|
41
|
+
* `useMonitorMapRouter()` entirely, so no `provideMonitorMapRouter(...)`
|
|
42
|
+
* ancestor is required.
|
|
43
|
+
*/
|
|
44
|
+
routerEscapeHatch?: boolean;
|
|
45
|
+
}
|
|
46
|
+
declare const MapShell: import("svelte").Component<MapShellProps, {}, "">;
|
|
47
|
+
type MapShell = ReturnType<typeof MapShell>;
|
|
48
|
+
export default MapShell;
|
package/package.json
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { Snippet } from "svelte";
|
|
3
|
-
import { onDestroy } from "svelte";
|
|
4
|
-
import
|
|
5
|
-
import Map from "$lib/map/Map.svelte";
|
|
6
|
-
import Menu from "$lib/map/Menu.svelte";
|
|
3
|
+
import { onDestroy, untrack } from "svelte";
|
|
4
|
+
import MapShell from "$lib/map/MapShell.svelte";
|
|
7
5
|
import MonitorsDisplayOptions from "$lib/monitors/components/MonitorsDisplayOptions.svelte";
|
|
8
6
|
import MapLayersDisplayOptions from "$lib/components/MapLayersDisplayOptions.svelte";
|
|
9
7
|
import MapStyleDisplayOptions from "$lib/map/MapStyleDisplayOptions.svelte";
|
|
10
|
-
import { mapManager } from "$lib/map/map.svelte";
|
|
11
8
|
import { monitorsManager } from "$lib/monitors/monitors.svelte";
|
|
12
9
|
import { monitorsMapIntegration } from "$lib/monitors/monitors-map-integration.svelte";
|
|
13
10
|
import { windMapIntegration } from "$lib/wind/wind.svelte";
|
|
@@ -33,8 +30,6 @@
|
|
|
33
30
|
|
|
34
31
|
const { route, navigate, basePath } = useMonitorMapRouter();
|
|
35
32
|
|
|
36
|
-
const TRANSITION_MS = 300;
|
|
37
|
-
|
|
38
33
|
const integrations: Array<SomeMapIntegration> = [
|
|
39
34
|
baseLayerSeperator,
|
|
40
35
|
collocationSitesMapIntegration,
|
|
@@ -59,10 +54,15 @@
|
|
|
59
54
|
// applies a later "?pollutant=" change (e.g. navigating in from elsewhere) to an already-running manager.
|
|
60
55
|
$effect(() => {
|
|
61
56
|
const urlPollutant = route.search.pollutant;
|
|
57
|
+
// Read monitorsManager.pollutant via untrack: this effect must only react to the URL
|
|
58
|
+
// changing (e.g. navigating in from elsewhere), not to monitorsManager.pollutant itself.
|
|
59
|
+
// Tracking it here would make a UI-driven pollutant change (e.g. the display-options
|
|
60
|
+
// toggle) re-trigger this effect before the manager->URL effect below can sync the URL,
|
|
61
|
+
// so this effect would see the still-stale URL and immediately revert the user's change.
|
|
62
62
|
if (
|
|
63
63
|
monitorsManager.initialized &&
|
|
64
64
|
(urlPollutant === "pm25" || urlPollutant === "o3") &&
|
|
65
|
-
monitorsManager.pollutant !== urlPollutant
|
|
65
|
+
untrack(() => monitorsManager.pollutant) !== urlPollutant
|
|
66
66
|
) {
|
|
67
67
|
monitorsManager.pollutant = urlPollutant;
|
|
68
68
|
}
|
|
@@ -76,22 +76,6 @@
|
|
|
76
76
|
}
|
|
77
77
|
});
|
|
78
78
|
|
|
79
|
-
$effect(() => {
|
|
80
|
-
if (mapManager.map && monitorsManager.initialized) {
|
|
81
|
-
mapManager.map.once("idle", () => disableLoadScreen());
|
|
82
|
-
}
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
$effect(() => {
|
|
86
|
-
if (panelOpen) {
|
|
87
|
-
// Container width snapped to open; resize after layout settles
|
|
88
|
-
requestAnimationFrame(() => mapManager.map?.resize());
|
|
89
|
-
} else {
|
|
90
|
-
// Container width snaps back after the transform transition ends
|
|
91
|
-
setTimeout(() => mapManager.map?.resize(), TRANSITION_MS);
|
|
92
|
-
}
|
|
93
|
-
});
|
|
94
|
-
|
|
95
79
|
// Clear selected icon scale when the detail panel closes
|
|
96
80
|
$effect(() => {
|
|
97
81
|
if (panelOpen) return;
|
|
@@ -101,75 +85,28 @@
|
|
|
101
85
|
onDestroy(() => {
|
|
102
86
|
monitorsManager.autoUpdate.stop();
|
|
103
87
|
});
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* HACK: Fix for escaping sv-router and allowing navigation to other pages,
|
|
107
|
-
* as well as navigating back
|
|
108
|
-
*/
|
|
109
|
-
const rootPath = basePath || "/";
|
|
110
|
-
const knownRoutes = [`${basePath}/monitor/`];
|
|
111
|
-
|
|
112
|
-
window.addEventListener("pageshow", (e) => {
|
|
113
|
-
if (e.persisted) window.location.reload();
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
document.addEventListener(
|
|
117
|
-
"click",
|
|
118
|
-
(e) => {
|
|
119
|
-
const anchor = e
|
|
120
|
-
.composedPath()
|
|
121
|
-
.find((el) => el instanceof HTMLAnchorElement) as HTMLAnchorElement;
|
|
122
|
-
if (!anchor) return;
|
|
123
|
-
const { pathname } = new URL(anchor.href);
|
|
124
|
-
const isKnown = pathname === rootPath || knownRoutes.some((r) => pathname.startsWith(r));
|
|
125
|
-
if (!isKnown) {
|
|
126
|
-
e.stopImmediatePropagation();
|
|
127
|
-
window.location.href = anchor.href;
|
|
128
|
-
}
|
|
129
|
-
},
|
|
130
|
-
{ capture: true }
|
|
131
|
-
);
|
|
132
88
|
</script>
|
|
133
89
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
90
|
+
<MapShell
|
|
91
|
+
{integrations}
|
|
92
|
+
ready={monitorsManager.initialized}
|
|
93
|
+
{panelOpen}
|
|
94
|
+
knownRoutes={[`${basePath}/monitor/`]}
|
|
95
|
+
{basePath}
|
|
96
|
+
>
|
|
97
|
+
{#snippet menu()}
|
|
98
|
+
<MonitorsDisplayOptions />
|
|
99
|
+
<EvStationsDisplayOptions />
|
|
100
|
+
<MapLayersDisplayOptions />
|
|
101
|
+
<MapStyleDisplayOptions />
|
|
102
|
+
{/snippet}
|
|
103
|
+
{#snippet overlays()}
|
|
141
104
|
<div class="pointer-events-none absolute bottom-0 left-0 z-10">
|
|
142
105
|
<MapLegend />
|
|
143
106
|
</div>
|
|
144
|
-
<div class="absolute top-4 left-
|
|
145
|
-
<
|
|
146
|
-
<Menu>
|
|
147
|
-
<MonitorsDisplayOptions />
|
|
148
|
-
<EvStationsDisplayOptions />
|
|
149
|
-
<MapLayersDisplayOptions />
|
|
150
|
-
<MapStyleDisplayOptions />
|
|
151
|
-
</Menu>
|
|
152
|
-
</div>
|
|
153
|
-
<div class="absolute left-16">
|
|
154
|
-
<Search />
|
|
155
|
-
</div>
|
|
156
|
-
</div>
|
|
157
|
-
</div>
|
|
158
|
-
<div
|
|
159
|
-
class={[
|
|
160
|
-
"panel-containr w-full shrink-0 overflow-hidden",
|
|
161
|
-
panelOpen ? "h-1/2 md:h-full md:w-1/3" : "md:w-0"
|
|
162
|
-
]}
|
|
163
|
-
>
|
|
164
|
-
<div
|
|
165
|
-
class={[
|
|
166
|
-
"panel-contet h-full duration-300 ease-in-out",
|
|
167
|
-
panelOpen
|
|
168
|
-
? "translate-y-0 md:translate-x-0"
|
|
169
|
-
: "translate-y-full md:translate-x-full md:translate-y-0"
|
|
170
|
-
]}
|
|
171
|
-
>
|
|
172
|
-
{@render children()}
|
|
107
|
+
<div class="absolute top-4 left-20 z-10">
|
|
108
|
+
<Search />
|
|
173
109
|
</div>
|
|
174
|
-
|
|
175
|
-
|
|
110
|
+
{/snippet}
|
|
111
|
+
{@render children()}
|
|
112
|
+
</MapShell>
|
package/src/lib/index.ts
CHANGED
|
@@ -55,6 +55,7 @@ export { hmsSmokeMapIntegration } from "./hms/hms-smoke-map-integration.svelte";
|
|
|
55
55
|
|
|
56
56
|
// Components
|
|
57
57
|
export { default as Map } from "./map/Map.svelte";
|
|
58
|
+
export { default as MapShell, type MapShellProps } from "./map/MapShell.svelte";
|
|
58
59
|
export { default as Menu } from "./map/Menu.svelte";
|
|
59
60
|
export {
|
|
60
61
|
default as LoadScreen,
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
import type { Snippet } from "svelte";
|
|
3
|
+
import type { SomeMapIntegration } from "$lib/map/integrations/types";
|
|
4
|
+
|
|
5
|
+
export interface MapShellProps {
|
|
6
|
+
/** Map integrations to register, e.g. [monitorsMapIntegration] */
|
|
7
|
+
integrations: Array<SomeMapIntegration>;
|
|
8
|
+
/**
|
|
9
|
+
* True once the caller's own data managers have finished their initial
|
|
10
|
+
* load. The shell hides the load screen once the map is idle AND this
|
|
11
|
+
* is true.
|
|
12
|
+
*/
|
|
13
|
+
ready: boolean;
|
|
14
|
+
/** True when the routed detail panel (`children`) should be visible */
|
|
15
|
+
panelOpen: boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Path prefixes that are part of this map's own routed panel (e.g.
|
|
18
|
+
* [`${basePath}/monitor/`]) — internal navigation to these should not
|
|
19
|
+
* trigger the escape-hatch full-page navigation below.
|
|
20
|
+
*/
|
|
21
|
+
knownRoutes?: Array<string>;
|
|
22
|
+
/** Rendered inside the display-options menu toggle */
|
|
23
|
+
menu?: Snippet;
|
|
24
|
+
/** Freeform absolutely-positioned content over the map (legend, search, etc.) */
|
|
25
|
+
overlays?: Snippet;
|
|
26
|
+
/** The routed detail panel content */
|
|
27
|
+
children?: Snippet;
|
|
28
|
+
/**
|
|
29
|
+
* Base path to use for the sv-router escape-hatch's "known" root route.
|
|
30
|
+
* If provided, used directly instead of reading `basePath` from
|
|
31
|
+
* `useMonitorMapRouter()`. Only relevant when `routerEscapeHatch` is
|
|
32
|
+
* enabled.
|
|
33
|
+
*/
|
|
34
|
+
basePath?: string;
|
|
35
|
+
/**
|
|
36
|
+
* Whether to install the sv-router escape-hatch (`pageshow`/`click`
|
|
37
|
+
* listeners that force a full-page navigation for links outside this
|
|
38
|
+
* map's own routes). Defaults to `true`, preserving the existing
|
|
39
|
+
* full-page-map behavior. When `true` and no `basePath` prop is given,
|
|
40
|
+
* `MapShell` falls back to reading `basePath` from
|
|
41
|
+
* `useMonitorMapRouter()`. Set to `false` to embed `MapShell` inside a
|
|
42
|
+
* host app with its own routing — this also skips calling
|
|
43
|
+
* `useMonitorMapRouter()` entirely, so no `provideMonitorMapRouter(...)`
|
|
44
|
+
* ancestor is required.
|
|
45
|
+
*/
|
|
46
|
+
routerEscapeHatch?: boolean;
|
|
47
|
+
}
|
|
48
|
+
</script>
|
|
49
|
+
|
|
50
|
+
<script lang="ts">
|
|
51
|
+
import LoadScreen, { disable as disableLoadScreen } from "$lib/LoadScreen.svelte";
|
|
52
|
+
import Map from "$lib/map/Map.svelte";
|
|
53
|
+
import Menu from "$lib/map/Menu.svelte";
|
|
54
|
+
import { mapManager } from "$lib/map/map.svelte";
|
|
55
|
+
import { useMonitorMapRouter } from "$lib/router-context";
|
|
56
|
+
|
|
57
|
+
let {
|
|
58
|
+
integrations,
|
|
59
|
+
ready,
|
|
60
|
+
panelOpen,
|
|
61
|
+
knownRoutes = [],
|
|
62
|
+
menu,
|
|
63
|
+
overlays,
|
|
64
|
+
children,
|
|
65
|
+
basePath: providedBasePath,
|
|
66
|
+
routerEscapeHatch = true
|
|
67
|
+
}: MapShellProps = $props();
|
|
68
|
+
|
|
69
|
+
const basePath = providedBasePath ?? (routerEscapeHatch ? useMonitorMapRouter().basePath : "");
|
|
70
|
+
const TRANSITION_MS = 300;
|
|
71
|
+
|
|
72
|
+
$effect(() => {
|
|
73
|
+
// mapManager.map is only ever assigned once its own initial "load" has
|
|
74
|
+
// already fired (see initializeMap in map.svelte.ts), so by the time this
|
|
75
|
+
// effect sees a non-null map, it's already safe to reveal — waiting for a
|
|
76
|
+
// further "idle" event here is unreliable since one may never fire again.
|
|
77
|
+
if (mapManager.map && ready) {
|
|
78
|
+
disableLoadScreen();
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
$effect(() => {
|
|
83
|
+
if (panelOpen) {
|
|
84
|
+
// Container width snapped to open; resize after layout settles
|
|
85
|
+
requestAnimationFrame(() => mapManager.map?.resize());
|
|
86
|
+
} else {
|
|
87
|
+
// Container width snaps back after the transform transition ends
|
|
88
|
+
setTimeout(() => mapManager.map?.resize(), TRANSITION_MS);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* HACK: Fix for escaping sv-router and allowing navigation to other pages,
|
|
94
|
+
* as well as navigating back
|
|
95
|
+
*/
|
|
96
|
+
if (routerEscapeHatch) {
|
|
97
|
+
const rootPath = basePath || "/";
|
|
98
|
+
|
|
99
|
+
window.addEventListener("pageshow", (e) => {
|
|
100
|
+
if (e.persisted) window.location.reload();
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
document.addEventListener(
|
|
104
|
+
"click",
|
|
105
|
+
(e) => {
|
|
106
|
+
const anchor = e
|
|
107
|
+
.composedPath()
|
|
108
|
+
.find((el) => el instanceof HTMLAnchorElement) as HTMLAnchorElement;
|
|
109
|
+
if (!anchor) return;
|
|
110
|
+
const { pathname } = new URL(anchor.href);
|
|
111
|
+
const isKnown = pathname === rootPath || knownRoutes.some((r) => pathname.startsWith(r));
|
|
112
|
+
if (!isKnown) {
|
|
113
|
+
e.stopImmediatePropagation();
|
|
114
|
+
window.location.href = anchor.href;
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
{ capture: true }
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
</script>
|
|
121
|
+
|
|
122
|
+
<div class="relative flex h-full w-full flex-col md:flex-row">
|
|
123
|
+
<LoadScreen />
|
|
124
|
+
<div class="relative flex-1 overflow-hidden">
|
|
125
|
+
<Map {integrations} />
|
|
126
|
+
{@render overlays?.()}
|
|
127
|
+
{#if menu}
|
|
128
|
+
<div class="absolute top-4 left-4 z-10">
|
|
129
|
+
<Menu>{@render menu()}</Menu>
|
|
130
|
+
</div>
|
|
131
|
+
{/if}
|
|
132
|
+
</div>
|
|
133
|
+
<div
|
|
134
|
+
class={[
|
|
135
|
+
"panel-containr w-full shrink-0 overflow-hidden",
|
|
136
|
+
panelOpen ? "h-1/2 md:h-full md:w-1/3" : "md:w-0"
|
|
137
|
+
]}
|
|
138
|
+
>
|
|
139
|
+
<div
|
|
140
|
+
class={[
|
|
141
|
+
"panel-contet h-full duration-300 ease-in-out",
|
|
142
|
+
panelOpen
|
|
143
|
+
? "translate-y-0 md:translate-x-0"
|
|
144
|
+
: "translate-y-full md:translate-x-full md:translate-y-0"
|
|
145
|
+
]}
|
|
146
|
+
>
|
|
147
|
+
{@render children?.()}
|
|
148
|
+
</div>
|
|
149
|
+
</div>
|
|
150
|
+
</div>
|