@sjvair/monitor-map 3.6.3 → 4.0.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/README.md +2 -2
- package/dist/lib/MonitorMapLayout.svelte +74 -36
- package/dist/lib/assets/icons/ozone/ozone-08-sunrise-haze.svg +1 -0
- package/dist/lib/assets/icons/pm25/pm25-02-particle-field.svg +1 -0
- package/dist/lib/collocation-sites/collocations-map-integration.svelte.d.ts +3 -0
- package/dist/lib/collocation-sites/collocations-map-integration.svelte.js +15 -1
- package/dist/lib/ev-stations/ev-stations-map-integration.svelte.js +27 -6
- package/dist/lib/hms/hms-fire-icon-manager.d.ts +8 -0
- package/dist/lib/hms/hms-fire-icon-manager.js +10 -1
- package/dist/lib/hms/hms-fire-map-integration.svelte.js +1 -1
- package/dist/lib/hms/hms-smoke-map-integration.svelte.d.ts +20 -0
- package/dist/lib/hms/hms-smoke-map-integration.svelte.js +22 -7
- package/dist/lib/index.d.ts +21 -7
- package/dist/lib/index.js +22 -7
- package/dist/lib/legend/Legend.svelte +40 -0
- package/dist/lib/legend/Legend.svelte.d.ts +7 -0
- package/dist/lib/legend/LegendBar.svelte +45 -0
- package/dist/lib/legend/LegendBar.svelte.d.ts +7 -0
- package/dist/lib/legend/legend-data.d.ts +40 -0
- package/dist/lib/legend/legend-data.js +65 -0
- package/dist/lib/map/MapShell.svelte +7 -6
- package/dist/lib/map/MapShell.svelte.d.ts +3 -1
- package/dist/lib/map/basemap.d.ts +14 -0
- package/dist/lib/map/basemap.js +10 -0
- package/dist/lib/map/integrations/map-geojson-integration.svelte.d.ts +6 -0
- package/dist/lib/map/integrations/map-geojson-integration.svelte.js +15 -2
- package/dist/lib/map/integrations/map-layer-integration.svelte.js +7 -3
- package/dist/lib/map/map-style-state.svelte.d.ts +13 -0
- package/dist/lib/map/map-style-state.svelte.js +35 -0
- package/dist/lib/map/map.svelte.d.ts +6 -0
- package/dist/lib/map/map.svelte.js +6 -0
- package/dist/lib/monitors/monitor-groups.d.ts +11 -0
- package/dist/lib/monitors/monitor-groups.js +7 -0
- package/dist/lib/monitors/monitor-utils.js +1 -3
- package/dist/lib/monitors/monitors-icon-manager.svelte.js +2 -5
- package/dist/lib/monitors/monitors-map-integration.svelte.d.ts +4 -4
- package/dist/lib/monitors/monitors-map-integration.svelte.js +45 -32
- package/dist/lib/monitors/monitors.svelte.d.ts +1 -0
- package/dist/lib/monitors/monitors.svelte.js +22 -0
- package/dist/lib/options/OptionsBar.svelte +96 -0
- package/dist/lib/options/OptionsBar.svelte.d.ts +10 -0
- package/dist/lib/options/OptionsGroup.svelte +12 -0
- package/dist/lib/options/OptionsGroup.svelte.d.ts +7 -0
- package/dist/lib/options/OptionsMenu.svelte +71 -0
- package/dist/lib/options/OptionsMenu.svelte.d.ts +15 -0
- package/dist/lib/options/group-state.d.ts +7 -0
- package/dist/lib/options/group-state.js +15 -0
- package/dist/lib/options/layout.d.ts +7 -0
- package/dist/lib/options/layout.js +9 -0
- package/dist/lib/options/map-option-defaults.d.ts +56 -0
- package/dist/lib/options/map-option-defaults.js +47 -0
- package/dist/lib/options/menus/LayersMenu.svelte +47 -0
- package/dist/lib/options/menus/LayersMenu.svelte.d.ts +3 -0
- package/dist/lib/options/menus/MonitorsMenu.svelte +68 -0
- package/dist/lib/options/menus/MonitorsMenu.svelte.d.ts +3 -0
- package/dist/lib/options/menus/OverlaysMenu.svelte +18 -0
- package/dist/lib/{MapLegend.svelte.d.ts → options/menus/OverlaysMenu.svelte.d.ts} +3 -3
- package/dist/lib/options/menus/PollutantMenu.svelte +89 -0
- package/dist/lib/options/menus/PollutantMenu.svelte.d.ts +3 -0
- package/dist/lib/options/menus/SettingsMenu.svelte +73 -0
- package/dist/lib/{hms/components/HMSFireLegend.svelte.d.ts → options/menus/SettingsMenu.svelte.d.ts} +3 -3
- package/dist/lib/options/options-state.svelte.d.ts +12 -0
- package/dist/lib/options/options-state.svelte.js +42 -0
- package/dist/lib/options/pollutant-param.d.ts +17 -0
- package/dist/lib/options/pollutant-param.js +14 -0
- package/dist/lib/options/reset-map-options.d.ts +2 -0
- package/dist/lib/options/reset-map-options.js +24 -0
- package/dist/lib/options/rows/CheckboxRow.svelte +51 -0
- package/dist/lib/options/rows/CheckboxRow.svelte.d.ts +13 -0
- package/dist/lib/options/rows/GroupRow.svelte +43 -0
- package/dist/lib/options/rows/GroupRow.svelte.d.ts +17 -0
- package/dist/lib/options/rows/MarkerIcon.svelte +10 -0
- package/dist/lib/options/rows/MarkerIcon.svelte.d.ts +8 -0
- package/dist/lib/options/rows/RadioRow.svelte +33 -0
- package/dist/lib/options/rows/RadioRow.svelte.d.ts +13 -0
- package/dist/lib/options/rows/SubmenuRow.svelte +22 -0
- package/dist/lib/options/rows/SubmenuRow.svelte.d.ts +11 -0
- package/dist/lib/search/Search.svelte +6 -6
- package/package.json +5 -3
- package/src/lib/MonitorMapLayout.svelte +74 -36
- package/src/lib/assets/icons/ozone/ozone-08-sunrise-haze.svg +1 -0
- package/src/lib/assets/icons/pm25/pm25-02-particle-field.svg +1 -0
- package/src/lib/collocation-sites/collocations-map-integration.svelte.ts +17 -1
- package/src/lib/ev-stations/ev-stations-map-integration.svelte.ts +26 -6
- package/src/lib/hms/hms-fire-icon-manager.ts +16 -2
- package/src/lib/hms/hms-fire-map-integration.svelte.ts +1 -1
- package/src/lib/hms/hms-smoke-map-integration.svelte.ts +25 -7
- package/src/lib/index.ts +31 -7
- package/src/lib/legend/Legend.svelte +40 -0
- package/src/lib/legend/LegendBar.svelte +45 -0
- package/src/lib/legend/legend-data.ts +101 -0
- package/src/lib/map/MapShell.svelte +7 -6
- package/src/lib/map/basemap.ts +24 -0
- package/src/lib/map/integrations/map-geojson-integration.svelte.ts +15 -2
- package/src/lib/map/integrations/map-layer-integration.svelte.ts +6 -3
- package/src/lib/map/map-style-state.svelte.ts +42 -0
- package/src/lib/map/map.svelte.ts +6 -0
- package/src/lib/monitors/monitor-groups.ts +20 -0
- package/src/lib/monitors/monitor-utils.ts +1 -5
- package/src/lib/monitors/monitors-icon-manager.svelte.ts +2 -8
- package/src/lib/monitors/monitors-map-integration.svelte.ts +39 -29
- package/src/lib/monitors/monitors.svelte.ts +22 -0
- package/src/lib/options/OptionsBar.svelte +96 -0
- package/src/lib/options/OptionsGroup.svelte +12 -0
- package/src/lib/options/OptionsMenu.svelte +71 -0
- package/src/lib/options/group-state.ts +16 -0
- package/src/lib/options/layout.ts +10 -0
- package/src/lib/options/map-option-defaults.ts +71 -0
- package/src/lib/options/menus/LayersMenu.svelte +47 -0
- package/src/lib/options/menus/MonitorsMenu.svelte +68 -0
- package/src/lib/options/menus/OverlaysMenu.svelte +18 -0
- package/src/lib/options/menus/PollutantMenu.svelte +89 -0
- package/src/lib/options/menus/SettingsMenu.svelte +73 -0
- package/src/lib/options/options-state.svelte.ts +48 -0
- package/src/lib/options/pollutant-param.ts +35 -0
- package/src/lib/options/reset-map-options.ts +28 -0
- package/src/lib/options/rows/CheckboxRow.svelte +51 -0
- package/src/lib/options/rows/GroupRow.svelte +43 -0
- package/src/lib/options/rows/MarkerIcon.svelte +10 -0
- package/src/lib/options/rows/RadioRow.svelte +33 -0
- package/src/lib/options/rows/SubmenuRow.svelte +22 -0
- package/src/lib/search/Search.svelte +6 -6
- package/dist/lib/MapLegend.svelte +0 -15
- package/dist/lib/collocation-sites/collocation-utils.d.ts +0 -3
- package/dist/lib/collocation-sites/collocation-utils.js +0 -30
- package/dist/lib/components/DisplayOption.svelte +0 -7
- package/dist/lib/components/DisplayOption.svelte.d.ts +0 -5
- package/dist/lib/components/MapLayersDisplayOptions.svelte +0 -42
- package/dist/lib/components/MapLayersDisplayOptions.svelte.d.ts +0 -18
- package/dist/lib/components/SegmentedControl.svelte +0 -52
- package/dist/lib/components/SegmentedControl.svelte.d.ts +0 -11
- package/dist/lib/components/ToggleSwitch.svelte +0 -93
- package/dist/lib/components/ToggleSwitch.svelte.d.ts +0 -9
- package/dist/lib/ev-stations/components/EvStationsDisplayOptions.svelte +0 -46
- package/dist/lib/ev-stations/components/EvStationsDisplayOptions.svelte.d.ts +0 -18
- package/dist/lib/hms/components/HMSFireLegend.svelte +0 -21
- package/dist/lib/map/MapStyleDisplayOptions.svelte +0 -61
- package/dist/lib/map/MapStyleDisplayOptions.svelte.d.ts +0 -3
- package/dist/lib/map/Menu.svelte +0 -65
- package/dist/lib/map/Menu.svelte.d.ts +0 -5
- package/dist/lib/monitors/components/MonitorMarkersLegend.svelte +0 -41
- package/dist/lib/monitors/components/MonitorMarkersLegend.svelte.d.ts +0 -3
- package/dist/lib/monitors/components/MonitorsDisplayOptions.svelte +0 -85
- package/dist/lib/monitors/components/MonitorsDisplayOptions.svelte.d.ts +0 -3
- package/src/lib/MapLegend.svelte +0 -15
- package/src/lib/collocation-sites/collocation-utils.ts +0 -41
- package/src/lib/components/DisplayOption.svelte +0 -7
- package/src/lib/components/MapLayersDisplayOptions.svelte +0 -42
- package/src/lib/components/SegmentedControl.svelte +0 -52
- package/src/lib/components/ToggleSwitch.svelte +0 -93
- package/src/lib/ev-stations/components/EvStationsDisplayOptions.svelte +0 -46
- package/src/lib/hms/components/HMSFireLegend.svelte +0 -21
- package/src/lib/map/MapStyleDisplayOptions.svelte +0 -61
- package/src/lib/map/Menu.svelte +0 -65
- package/src/lib/monitors/components/MonitorMarkersLegend.svelte +0 -41
- package/src/lib/monitors/components/MonitorsDisplayOptions.svelte +0 -85
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from "svelte";
|
|
3
|
+
import type { Attachment } from "svelte/attachments";
|
|
4
|
+
import { MenuIcon, XIcon } from "@lucide/svelte";
|
|
5
|
+
import { provideOptionsMenus } from "./options-state.svelte.js";
|
|
6
|
+
import { isWideLayout, WIDE_LAYOUT_QUERY } from "./layout";
|
|
7
|
+
|
|
8
|
+
interface Props {
|
|
9
|
+
/** OptionsGroup / OptionsMenu content */
|
|
10
|
+
children?: Snippet;
|
|
11
|
+
/** Rendered last in the toolbar (wide) or beside the menu button (narrow) */
|
|
12
|
+
search?: Snippet;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
let { children, search }: Props = $props();
|
|
16
|
+
|
|
17
|
+
const menus = provideOptionsMenus();
|
|
18
|
+
const panelId = "monitor-map-options-panel";
|
|
19
|
+
let panelOpen = $state(false);
|
|
20
|
+
|
|
21
|
+
// Toolbar only: clicks outside close any click-opened dropdown; clicks inside never do.
|
|
22
|
+
const closeOnOutsideClick: Attachment<HTMLElement> = (node) => {
|
|
23
|
+
function handler(e: MouseEvent) {
|
|
24
|
+
if (isWideLayout() && !node.contains(e.target as Node)) menus.closeAll();
|
|
25
|
+
}
|
|
26
|
+
document.addEventListener("click", handler);
|
|
27
|
+
return () => document.removeEventListener("click", handler);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// Open menus mean different things in the toolbar (dropdowns) and the panel (expanded
|
|
31
|
+
// sections), so crossing the breakpoint (e.g. rotating a phone) starts from a closed state.
|
|
32
|
+
$effect(() => {
|
|
33
|
+
const query = window.matchMedia(WIDE_LAYOUT_QUERY);
|
|
34
|
+
function onchange() {
|
|
35
|
+
menus.closeAll();
|
|
36
|
+
panelOpen = false;
|
|
37
|
+
}
|
|
38
|
+
query.addEventListener("change", onchange);
|
|
39
|
+
return () => query.removeEventListener("change", onchange);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
function onkeydown(e: KeyboardEvent) {
|
|
43
|
+
if (e.key !== "Escape") return;
|
|
44
|
+
panelOpen = false;
|
|
45
|
+
if (isWideLayout()) menus.closeAll();
|
|
46
|
+
}
|
|
47
|
+
</script>
|
|
48
|
+
|
|
49
|
+
<svelte:window {onkeydown} />
|
|
50
|
+
|
|
51
|
+
<!-- Covers the map area so the narrow panel can fill it; passes pointer events through. -->
|
|
52
|
+
<div class="pointer-events-none absolute inset-0 z-10">
|
|
53
|
+
<div
|
|
54
|
+
{@attach closeOnOutsideClick}
|
|
55
|
+
class="md:absolute md:top-4 md:right-16 md:left-4 md:flex md:flex-wrap md:items-start md:gap-2"
|
|
56
|
+
>
|
|
57
|
+
<div
|
|
58
|
+
class="pointer-events-auto flex items-start gap-2 max-md:absolute max-md:top-4 max-md:left-4 md:contents"
|
|
59
|
+
>
|
|
60
|
+
<button
|
|
61
|
+
type="button"
|
|
62
|
+
class="flex size-10 shrink-0 items-center justify-center rounded-md bg-white shadow md:hidden"
|
|
63
|
+
aria-label="Open map options"
|
|
64
|
+
aria-expanded={panelOpen}
|
|
65
|
+
aria-controls={panelId}
|
|
66
|
+
onclick={() => (panelOpen = true)}
|
|
67
|
+
>
|
|
68
|
+
<MenuIcon size={22} />
|
|
69
|
+
</button>
|
|
70
|
+
{#if search}
|
|
71
|
+
<div class="md:order-last">{@render search()}</div>
|
|
72
|
+
{/if}
|
|
73
|
+
</div>
|
|
74
|
+
<div
|
|
75
|
+
id={panelId}
|
|
76
|
+
class={[
|
|
77
|
+
"pointer-events-auto md:contents",
|
|
78
|
+
"max-md:absolute max-md:inset-0 max-md:z-20 max-md:flex max-md:flex-col max-md:gap-2 max-md:overflow-y-auto max-md:bg-white max-md:p-4 max-md:transition-[translate,visibility] max-md:duration-200",
|
|
79
|
+
!panelOpen && "max-md:invisible max-md:-translate-x-full"
|
|
80
|
+
]}
|
|
81
|
+
>
|
|
82
|
+
<div class="mb-2 flex items-center justify-between md:hidden">
|
|
83
|
+
<p class="text-lg font-semibold">Map Options</p>
|
|
84
|
+
<button
|
|
85
|
+
type="button"
|
|
86
|
+
class="flex size-9 items-center justify-center rounded-md hover:bg-gray-100"
|
|
87
|
+
aria-label="Close map options"
|
|
88
|
+
onclick={() => (panelOpen = false)}
|
|
89
|
+
>
|
|
90
|
+
<XIcon size={20} />
|
|
91
|
+
</button>
|
|
92
|
+
</div>
|
|
93
|
+
{@render children?.()}
|
|
94
|
+
</div>
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Snippet } from "svelte";
|
|
2
|
+
interface Props {
|
|
3
|
+
/** OptionsGroup / OptionsMenu content */
|
|
4
|
+
children?: Snippet;
|
|
5
|
+
/** Rendered last in the toolbar (wide) or beside the menu button (narrow) */
|
|
6
|
+
search?: Snippet;
|
|
7
|
+
}
|
|
8
|
+
declare const OptionsBar: import("svelte").Component<Props, {}, "">;
|
|
9
|
+
type OptionsBar = ReturnType<typeof OptionsBar>;
|
|
10
|
+
export default OptionsBar;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from "svelte";
|
|
3
|
+
|
|
4
|
+
let { children }: { children: Snippet } = $props();
|
|
5
|
+
</script>
|
|
6
|
+
|
|
7
|
+
<!-- Joined button cluster on the toolbar; a plain stack of sections in the panel. -->
|
|
8
|
+
<div
|
|
9
|
+
class="flex flex-col gap-2 md:flex-row md:gap-0 md:divide-x md:divide-gray-200 md:rounded-md md:bg-white md:shadow"
|
|
10
|
+
>
|
|
11
|
+
{@render children()}
|
|
12
|
+
</div>
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from "svelte";
|
|
3
|
+
import { ChevronDownIcon } from "@lucide/svelte";
|
|
4
|
+
import { useOptionsMenus } from "./options-state.svelte.js";
|
|
5
|
+
import { isWideLayout } from "./layout";
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
id: string;
|
|
9
|
+
label: string;
|
|
10
|
+
icon: Snippet;
|
|
11
|
+
children: Snippet;
|
|
12
|
+
disabled?: boolean;
|
|
13
|
+
/** Anchor the toolbar dropdown to the trigger's right edge */
|
|
14
|
+
alignRight?: boolean;
|
|
15
|
+
/** Show only the icon on the toolbar; the panel section still shows the label */
|
|
16
|
+
iconOnly?: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
let {
|
|
20
|
+
id,
|
|
21
|
+
label,
|
|
22
|
+
icon,
|
|
23
|
+
children,
|
|
24
|
+
disabled = false,
|
|
25
|
+
alignRight = false,
|
|
26
|
+
iconOnly = false
|
|
27
|
+
}: Props = $props();
|
|
28
|
+
|
|
29
|
+
const menus = useOptionsMenus();
|
|
30
|
+
const contentId = $derived(`monitor-map-options-${id}`);
|
|
31
|
+
const open = $derived(menus.isOpen(id) && !disabled);
|
|
32
|
+
</script>
|
|
33
|
+
|
|
34
|
+
<div
|
|
35
|
+
role="presentation"
|
|
36
|
+
class="group/menu relative md:first:[&>button]:rounded-l-md md:last:[&>button]:rounded-r-md"
|
|
37
|
+
onmouseenter={() => {
|
|
38
|
+
if (isWideLayout()) menus.closeOthers(id);
|
|
39
|
+
}}
|
|
40
|
+
>
|
|
41
|
+
<button
|
|
42
|
+
type="button"
|
|
43
|
+
{disabled}
|
|
44
|
+
aria-expanded={open}
|
|
45
|
+
aria-controls={contentId}
|
|
46
|
+
aria-label={iconOnly ? label : undefined}
|
|
47
|
+
class="flex h-10 w-full items-center gap-2 px-3 text-sm font-medium whitespace-nowrap select-none disabled:cursor-not-allowed max-md:rounded-md max-md:border max-md:border-gray-200 max-md:bg-white md:w-auto md:hover:bg-gray-100 md:disabled:hover:bg-transparent"
|
|
48
|
+
onclick={() => menus.toggle(id)}
|
|
49
|
+
>
|
|
50
|
+
<span class={["flex size-5 items-center justify-center", disabled && "opacity-50"]}>
|
|
51
|
+
{@render icon()}
|
|
52
|
+
</span>
|
|
53
|
+
<span class={[disabled && "opacity-50", iconOnly && "md:hidden"]}>{label}</span>
|
|
54
|
+
<span class={["ml-auto", disabled && "opacity-50", iconOnly && "md:hidden"]}>
|
|
55
|
+
<ChevronDownIcon size={16} />
|
|
56
|
+
</span>
|
|
57
|
+
</button>
|
|
58
|
+
<div
|
|
59
|
+
id={contentId}
|
|
60
|
+
class={[
|
|
61
|
+
"max-md:pt-1 md:absolute md:top-full md:z-20 md:min-w-64 md:pt-1",
|
|
62
|
+
alignRight ? "md:right-0" : "md:left-0",
|
|
63
|
+
open ? "block" : "hidden",
|
|
64
|
+
!disabled && "md:group-hover/menu:block md:group-has-[:focus-visible]/menu:block"
|
|
65
|
+
]}
|
|
66
|
+
>
|
|
67
|
+
<div class="py-1 md:max-h-[69vh] md:overflow-y-auto md:rounded-md md:bg-white md:shadow-lg">
|
|
68
|
+
{@render children()}
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Snippet } from "svelte";
|
|
2
|
+
interface Props {
|
|
3
|
+
id: string;
|
|
4
|
+
label: string;
|
|
5
|
+
icon: Snippet;
|
|
6
|
+
children: Snippet;
|
|
7
|
+
disabled?: boolean;
|
|
8
|
+
/** Anchor the toolbar dropdown to the trigger's right edge */
|
|
9
|
+
alignRight?: boolean;
|
|
10
|
+
/** Show only the icon on the toolbar; the panel section still shows the label */
|
|
11
|
+
iconOnly?: boolean;
|
|
12
|
+
}
|
|
13
|
+
declare const OptionsMenu: import("svelte").Component<Props, {}, "">;
|
|
14
|
+
type OptionsMenu = ReturnType<typeof OptionsMenu>;
|
|
15
|
+
export default OptionsMenu;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export type GroupState = "checked" | "unchecked" | "indeterminate" | "empty";
|
|
2
|
+
/** Select-all state of a group, computed only from its currently visible rows. */
|
|
3
|
+
export declare function groupState(values: ReadonlyArray<boolean>): GroupState;
|
|
4
|
+
/** Applies a select-all toggle to the given (visible) rows only. */
|
|
5
|
+
export declare function setGroup(options: ReadonlyArray<{
|
|
6
|
+
value: boolean;
|
|
7
|
+
}>, value: boolean): void;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** Select-all state of a group, computed only from its currently visible rows. */
|
|
2
|
+
export function groupState(values) {
|
|
3
|
+
if (values.length === 0)
|
|
4
|
+
return "empty";
|
|
5
|
+
const checkedCount = values.filter(Boolean).length;
|
|
6
|
+
if (checkedCount === 0)
|
|
7
|
+
return "unchecked";
|
|
8
|
+
return checkedCount === values.length ? "checked" : "indeterminate";
|
|
9
|
+
}
|
|
10
|
+
/** Applies a select-all toggle to the given (visible) rows only. */
|
|
11
|
+
export function setGroup(options, value) {
|
|
12
|
+
for (const option of options) {
|
|
13
|
+
option.value = value;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tailwind's default `md` breakpoint (48rem). Layout itself is pure CSS (`md:` / `max-md:`);
|
|
3
|
+
* this is only read at event time for the few behaviors that differ between the toolbar and
|
|
4
|
+
* the options panel.
|
|
5
|
+
*/
|
|
6
|
+
export declare const WIDE_LAYOUT_QUERY = "(min-width: 48rem)";
|
|
7
|
+
export declare function isWideLayout(): boolean;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tailwind's default `md` breakpoint (48rem). Layout itself is pure CSS (`md:` / `max-md:`);
|
|
3
|
+
* this is only read at event time for the few behaviors that differ between the toolbar and
|
|
4
|
+
* the options panel.
|
|
5
|
+
*/
|
|
6
|
+
export const WIDE_LAYOUT_QUERY = "(min-width: 48rem)";
|
|
7
|
+
export function isWideLayout() {
|
|
8
|
+
return window.matchMedia(WIDE_LAYOUT_QUERY).matches;
|
|
9
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { MonitorsPollutant } from "../monitors/types";
|
|
2
|
+
import { type PollutantTargets } from "./pollutant-param";
|
|
3
|
+
/**
|
|
4
|
+
* Defaults restored by "Reset Map Options". Keep in sync with the constructor defaults in each
|
|
5
|
+
* integration (monitors/EV display options, `enabled`, `clustered`) so a reset matches a fresh load.
|
|
6
|
+
*/
|
|
7
|
+
export declare const MAP_OPTION_DEFAULTS: {
|
|
8
|
+
readonly monitorTypes: {
|
|
9
|
+
readonly airnow: true;
|
|
10
|
+
readonly aqlite: true;
|
|
11
|
+
readonly aqview: true;
|
|
12
|
+
readonly bam1022: true;
|
|
13
|
+
readonly airgradient: true;
|
|
14
|
+
readonly purpleair: true;
|
|
15
|
+
readonly vozbox: true;
|
|
16
|
+
readonly inactive: false;
|
|
17
|
+
readonly inside: false;
|
|
18
|
+
};
|
|
19
|
+
readonly clustered: true;
|
|
20
|
+
readonly collocationSites: false;
|
|
21
|
+
readonly fireAndSmoke: true;
|
|
22
|
+
readonly evStations: false;
|
|
23
|
+
readonly evLevels: {
|
|
24
|
+
readonly lvl2: true;
|
|
25
|
+
readonly lvl3: true;
|
|
26
|
+
};
|
|
27
|
+
readonly wind: false;
|
|
28
|
+
};
|
|
29
|
+
type Toggle = {
|
|
30
|
+
enabled: boolean;
|
|
31
|
+
};
|
|
32
|
+
type Option = {
|
|
33
|
+
value: boolean;
|
|
34
|
+
};
|
|
35
|
+
export interface MapOptionTargets extends PollutantTargets {
|
|
36
|
+
integration: PollutantTargets["integration"] & {
|
|
37
|
+
clustered: boolean;
|
|
38
|
+
displayOptions: Record<string, Option>;
|
|
39
|
+
};
|
|
40
|
+
collocationSites: Toggle;
|
|
41
|
+
hmsFire: Toggle;
|
|
42
|
+
hmsSmoke: Toggle;
|
|
43
|
+
evStations: Toggle & {
|
|
44
|
+
clustered: boolean;
|
|
45
|
+
displayOptions: {
|
|
46
|
+
lvl2: Option;
|
|
47
|
+
lvl3: Option;
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
wind: Toggle;
|
|
51
|
+
mapStyle: {
|
|
52
|
+
reset(): void;
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
export declare function applyMapOptionDefaults(targets: MapOptionTargets, defaultPollutant: MonitorsPollutant | null): void;
|
|
56
|
+
export {};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { applyPollutantSelection } from "./pollutant-param";
|
|
2
|
+
/**
|
|
3
|
+
* Defaults restored by "Reset Map Options". Keep in sync with the constructor defaults in each
|
|
4
|
+
* integration (monitors/EV display options, `enabled`, `clustered`) so a reset matches a fresh load.
|
|
5
|
+
*/
|
|
6
|
+
export const MAP_OPTION_DEFAULTS = {
|
|
7
|
+
monitorTypes: {
|
|
8
|
+
airnow: true,
|
|
9
|
+
aqlite: true,
|
|
10
|
+
aqview: true,
|
|
11
|
+
bam1022: true,
|
|
12
|
+
airgradient: true,
|
|
13
|
+
purpleair: true,
|
|
14
|
+
vozbox: true,
|
|
15
|
+
inactive: false,
|
|
16
|
+
inside: false
|
|
17
|
+
},
|
|
18
|
+
clustered: true,
|
|
19
|
+
collocationSites: false,
|
|
20
|
+
fireAndSmoke: true,
|
|
21
|
+
evStations: false,
|
|
22
|
+
evLevels: { lvl2: true, lvl3: true },
|
|
23
|
+
wind: false
|
|
24
|
+
};
|
|
25
|
+
export function applyMapOptionDefaults(targets, defaultPollutant) {
|
|
26
|
+
if (defaultPollutant) {
|
|
27
|
+
applyPollutantSelection(defaultPollutant, targets);
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
targets.integration.enabled = true;
|
|
31
|
+
}
|
|
32
|
+
for (const [key, value] of Object.entries(MAP_OPTION_DEFAULTS.monitorTypes)) {
|
|
33
|
+
const option = targets.integration.displayOptions[key];
|
|
34
|
+
if (option)
|
|
35
|
+
option.value = value;
|
|
36
|
+
}
|
|
37
|
+
targets.integration.clustered = MAP_OPTION_DEFAULTS.clustered;
|
|
38
|
+
targets.collocationSites.enabled = MAP_OPTION_DEFAULTS.collocationSites;
|
|
39
|
+
targets.hmsFire.enabled = MAP_OPTION_DEFAULTS.fireAndSmoke;
|
|
40
|
+
targets.hmsSmoke.enabled = MAP_OPTION_DEFAULTS.fireAndSmoke;
|
|
41
|
+
targets.evStations.enabled = MAP_OPTION_DEFAULTS.evStations;
|
|
42
|
+
targets.evStations.clustered = MAP_OPTION_DEFAULTS.clustered;
|
|
43
|
+
targets.evStations.displayOptions.lvl2.value = MAP_OPTION_DEFAULTS.evLevels.lvl2;
|
|
44
|
+
targets.evStations.displayOptions.lvl3.value = MAP_OPTION_DEFAULTS.evLevels.lvl3;
|
|
45
|
+
targets.wind.enabled = MAP_OPTION_DEFAULTS.wind;
|
|
46
|
+
targets.mapStyle.reset();
|
|
47
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { FlameIcon, LayersIcon, EvChargerIcon } from "@lucide/svelte";
|
|
3
|
+
import OptionsMenu from "../OptionsMenu.svelte";
|
|
4
|
+
import CheckboxRow from "../rows/CheckboxRow.svelte";
|
|
5
|
+
import SubmenuRow from "../rows/SubmenuRow.svelte";
|
|
6
|
+
import MarkerIcon from "../rows/MarkerIcon.svelte";
|
|
7
|
+
import { hmsFireMapIntegration } from "../../hms/hms-fire-map-integration.svelte";
|
|
8
|
+
import { hmsSmokeMapIntegration } from "../../hms/hms-smoke-map-integration.svelte";
|
|
9
|
+
import { evStationsMapIntegration } from "../../ev-stations/ev-stations-map-integration.svelte";
|
|
10
|
+
|
|
11
|
+
const levels = $derived(evStationsMapIntegration.displayOptions);
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<OptionsMenu id="layers" label="Layers">
|
|
15
|
+
{#snippet icon()}
|
|
16
|
+
<LayersIcon size={20} color="#708090" />
|
|
17
|
+
{/snippet}
|
|
18
|
+
<CheckboxRow
|
|
19
|
+
label="Fire & Smoke"
|
|
20
|
+
bind:checked={
|
|
21
|
+
() => hmsFireMapIntegration.enabled && hmsSmokeMapIntegration.enabled,
|
|
22
|
+
(value) => {
|
|
23
|
+
hmsFireMapIntegration.enabled = value;
|
|
24
|
+
hmsSmokeMapIntegration.enabled = value;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
>
|
|
28
|
+
{#snippet icon()}
|
|
29
|
+
<FlameIcon size={18} color="#ff4500" />
|
|
30
|
+
{/snippet}
|
|
31
|
+
</CheckboxRow>
|
|
32
|
+
<SubmenuRow label="EV Chargers" bind:checked={evStationsMapIntegration.enabled}>
|
|
33
|
+
{#snippet icon()}
|
|
34
|
+
<EvChargerIcon size={18} color="#708090" />
|
|
35
|
+
{/snippet}
|
|
36
|
+
<CheckboxRow label="Level 2" bind:checked={evStationsMapIntegration.displayOptions.lvl2.value}>
|
|
37
|
+
{#snippet icon()}
|
|
38
|
+
<MarkerIcon icon={levels.lvl2.icon} class="w-5" />
|
|
39
|
+
{/snippet}
|
|
40
|
+
</CheckboxRow>
|
|
41
|
+
<CheckboxRow label="Level 3" bind:checked={evStationsMapIntegration.displayOptions.lvl3.value}>
|
|
42
|
+
{#snippet icon()}
|
|
43
|
+
<MarkerIcon icon={levels.lvl3.icon} class="w-5" />
|
|
44
|
+
{/snippet}
|
|
45
|
+
</CheckboxRow>
|
|
46
|
+
</SubmenuRow>
|
|
47
|
+
</OptionsMenu>
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { CrosshairIcon, RadioTowerIcon } from "@lucide/svelte";
|
|
3
|
+
import OptionsMenu from "../OptionsMenu.svelte";
|
|
4
|
+
import CheckboxRow from "../rows/CheckboxRow.svelte";
|
|
5
|
+
import GroupRow from "../rows/GroupRow.svelte";
|
|
6
|
+
import MarkerIcon from "../rows/MarkerIcon.svelte";
|
|
7
|
+
import { monitorsManager } from "../../monitors/monitors.svelte";
|
|
8
|
+
import { monitorsMapIntegration } from "../../monitors/monitors-map-integration.svelte";
|
|
9
|
+
import { collocationSitesMapIntegration } from "../../collocation-sites/collocations-map-integration.svelte";
|
|
10
|
+
import {
|
|
11
|
+
LOW_COST_TYPES,
|
|
12
|
+
REFERENCE_GRADE_TYPES,
|
|
13
|
+
typeSupportsPollutant,
|
|
14
|
+
type MonitorGroupType
|
|
15
|
+
} from "../../monitors/monitor-groups";
|
|
16
|
+
|
|
17
|
+
// Rows for types that don't report the current pollutant are hidden (meta-driven).
|
|
18
|
+
function visibleRows(types: ReadonlyArray<MonitorGroupType>) {
|
|
19
|
+
const displayOptions = monitorsMapIntegration.displayOptions;
|
|
20
|
+
return types
|
|
21
|
+
.filter((type) =>
|
|
22
|
+
typeSupportsPollutant(monitorsManager.meta, type, monitorsManager.pollutant)
|
|
23
|
+
)
|
|
24
|
+
.map((type) => ({
|
|
25
|
+
key: type,
|
|
26
|
+
label: displayOptions[type].label,
|
|
27
|
+
option: displayOptions[type]
|
|
28
|
+
}));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const referenceGrade = $derived(visibleRows(REFERENCE_GRADE_TYPES));
|
|
32
|
+
const lowCost = $derived(visibleRows(LOW_COST_TYPES));
|
|
33
|
+
const icons = monitorsMapIntegration.icons;
|
|
34
|
+
</script>
|
|
35
|
+
|
|
36
|
+
<OptionsMenu id="monitors" label="Monitors" disabled={!monitorsMapIntegration.enabled}>
|
|
37
|
+
{#snippet icon()}
|
|
38
|
+
<RadioTowerIcon size={20} color="#4682b4" />
|
|
39
|
+
{/snippet}
|
|
40
|
+
<GroupRow label="Reference-grade" options={referenceGrade}>
|
|
41
|
+
{#snippet icon()}
|
|
42
|
+
<MarkerIcon icon={icons.get("outside-display-triangle")} />
|
|
43
|
+
{/snippet}
|
|
44
|
+
</GroupRow>
|
|
45
|
+
<GroupRow label="Low-cost sensors" options={lowCost}>
|
|
46
|
+
{#snippet icon()}
|
|
47
|
+
<MarkerIcon icon={icons.get("outside-display-circle")} />
|
|
48
|
+
{/snippet}
|
|
49
|
+
</GroupRow>
|
|
50
|
+
<hr class="my-1 border-gray-200" />
|
|
51
|
+
<CheckboxRow label="Inactive" bind:checked={monitorsMapIntegration.displayOptions.inactive.value}>
|
|
52
|
+
{#snippet icon()}
|
|
53
|
+
<MarkerIcon icon={monitorsMapIntegration.displayOptions.inactive.icon} />
|
|
54
|
+
{/snippet}
|
|
55
|
+
</CheckboxRow>
|
|
56
|
+
<CheckboxRow label="Inside" bind:checked={monitorsMapIntegration.displayOptions.inside.value}>
|
|
57
|
+
{#snippet icon()}
|
|
58
|
+
<MarkerIcon icon={monitorsMapIntegration.displayOptions.inside.icon} />
|
|
59
|
+
{/snippet}
|
|
60
|
+
</CheckboxRow>
|
|
61
|
+
{#if monitorsManager.pollutant === "pm25"}
|
|
62
|
+
<CheckboxRow label="Collocation Sites" bind:checked={collocationSitesMapIntegration.enabled}>
|
|
63
|
+
{#snippet icon()}
|
|
64
|
+
<CrosshairIcon size={16} color="#4A5FC6" />
|
|
65
|
+
{/snippet}
|
|
66
|
+
</CheckboxRow>
|
|
67
|
+
{/if}
|
|
68
|
+
</OptionsMenu>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { SquareStackIcon, WindIcon } from "@lucide/svelte";
|
|
3
|
+
import OptionsMenu from "../OptionsMenu.svelte";
|
|
4
|
+
import CheckboxRow from "../rows/CheckboxRow.svelte";
|
|
5
|
+
import { windMapIntegration } from "../../wind/wind.svelte";
|
|
6
|
+
</script>
|
|
7
|
+
|
|
8
|
+
<!-- Independent checkboxes: several overlays may be on at once. -->
|
|
9
|
+
<OptionsMenu id="overlays" label="Overlays" alignRight>
|
|
10
|
+
{#snippet icon()}
|
|
11
|
+
<SquareStackIcon size={20} color="#708090" />
|
|
12
|
+
{/snippet}
|
|
13
|
+
<CheckboxRow label="Wind" bind:checked={windMapIntegration.enabled}>
|
|
14
|
+
{#snippet icon()}
|
|
15
|
+
<WindIcon size={18} color="#1e90ff" />
|
|
16
|
+
{/snippet}
|
|
17
|
+
</CheckboxRow>
|
|
18
|
+
</OptionsMenu>
|
|
@@ -11,8 +11,8 @@ interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> =
|
|
|
11
11
|
};
|
|
12
12
|
z_$$bindings?: Bindings;
|
|
13
13
|
}
|
|
14
|
-
declare const
|
|
14
|
+
declare const OverlaysMenu: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
|
|
15
15
|
[evt: string]: CustomEvent<any>;
|
|
16
16
|
}, {}, {}, string>;
|
|
17
|
-
type
|
|
18
|
-
export default
|
|
17
|
+
type OverlaysMenu = InstanceType<typeof OverlaysMenu>;
|
|
18
|
+
export default OverlaysMenu;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { BanIcon } from "@lucide/svelte";
|
|
3
|
+
import OptionsMenu from "../OptionsMenu.svelte";
|
|
4
|
+
import RadioRow from "../rows/RadioRow.svelte";
|
|
5
|
+
import { useOptionsMenus } from "../options-state.svelte.js";
|
|
6
|
+
import { isWideLayout } from "../layout";
|
|
7
|
+
import {
|
|
8
|
+
applyPollutantSelection,
|
|
9
|
+
currentSelection,
|
|
10
|
+
type PollutantSelection
|
|
11
|
+
} from "../pollutant-param";
|
|
12
|
+
import { monitorsManager } from "../../monitors/monitors.svelte";
|
|
13
|
+
import { monitorsMapIntegration } from "../../monitors/monitors-map-integration.svelte";
|
|
14
|
+
import pm25Icon from "../../assets/icons/pm25/pm25-02-particle-field.svg";
|
|
15
|
+
import ozoneIcon from "../../assets/icons/ozone/ozone-08-sunrise-haze.svg";
|
|
16
|
+
|
|
17
|
+
interface PollutantOption {
|
|
18
|
+
value: PollutantSelection;
|
|
19
|
+
label: string;
|
|
20
|
+
unit?: string;
|
|
21
|
+
help?: string;
|
|
22
|
+
/** Lucide component, or an image URL for multi-color SVG icons */
|
|
23
|
+
icon: typeof BanIcon | string;
|
|
24
|
+
/** Tint for Lucide icons; image icons carry their own colors */
|
|
25
|
+
color?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const options: Array<PollutantOption> = [
|
|
29
|
+
{
|
|
30
|
+
value: "pm25",
|
|
31
|
+
label: "PM2.5",
|
|
32
|
+
unit: "µg/m³",
|
|
33
|
+
help: "Fine particles from smoke, dust, and vehicle exhaust.",
|
|
34
|
+
icon: pm25Icon
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
value: "o3",
|
|
38
|
+
label: "Ozone",
|
|
39
|
+
unit: "ppb",
|
|
40
|
+
help: "Ground-level gas that forms in sunlight on hot days.",
|
|
41
|
+
icon: ozoneIcon
|
|
42
|
+
},
|
|
43
|
+
{ value: "none", label: "None", icon: BanIcon, color: "#778899" }
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
const menus = useOptionsMenus();
|
|
47
|
+
const selection = $derived(
|
|
48
|
+
currentSelection(monitorsManager.pollutant, monitorsMapIntegration.enabled)
|
|
49
|
+
);
|
|
50
|
+
const selected = $derived(options.find((o) => o.value === selection) ?? options[0]);
|
|
51
|
+
|
|
52
|
+
function select(value: PollutantSelection) {
|
|
53
|
+
applyPollutantSelection(value, {
|
|
54
|
+
manager: monitorsManager,
|
|
55
|
+
integration: monitorsMapIntegration
|
|
56
|
+
});
|
|
57
|
+
// Toolbar dropdown closes on pick; the panel section stays open.
|
|
58
|
+
if (isWideLayout()) menus.close("pollutant");
|
|
59
|
+
}
|
|
60
|
+
</script>
|
|
61
|
+
|
|
62
|
+
{#snippet pollutantIcon(option: PollutantOption, size: number)}
|
|
63
|
+
{#if typeof option.icon === "string"}
|
|
64
|
+
<img src={option.icon} alt="" width={size} height={size} class="shrink-0" />
|
|
65
|
+
{:else}
|
|
66
|
+
<option.icon {size} color={option.color} />
|
|
67
|
+
{/if}
|
|
68
|
+
{/snippet}
|
|
69
|
+
|
|
70
|
+
<OptionsMenu id="pollutant" label={selected.label}>
|
|
71
|
+
{#snippet icon()}
|
|
72
|
+
{@render pollutantIcon(selected, 20)}
|
|
73
|
+
{/snippet}
|
|
74
|
+
{#each options as option, i (option.value)}
|
|
75
|
+
{#if i > 0}<hr class="my-1 border-gray-200" />{/if}
|
|
76
|
+
<RadioRow
|
|
77
|
+
name="monitor-map-pollutant"
|
|
78
|
+
label={option.label}
|
|
79
|
+
unit={option.unit}
|
|
80
|
+
help={option.help}
|
|
81
|
+
checked={selection === option.value}
|
|
82
|
+
onselect={() => select(option.value)}
|
|
83
|
+
>
|
|
84
|
+
{#snippet icon()}
|
|
85
|
+
{@render pollutantIcon(option, 18)}
|
|
86
|
+
{/snippet}
|
|
87
|
+
</RadioRow>
|
|
88
|
+
{/each}
|
|
89
|
+
</OptionsMenu>
|