@smartnet360/svelte-components 0.0.84 → 0.0.86
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/apps/antenna-pattern/components/AntennaControls.svelte +1 -106
- package/dist/apps/antenna-pattern/components/AntennaDiagrams.svelte +0 -36
- package/dist/apps/antenna-pattern/components/AntennaSettingsModal.svelte +0 -2
- package/dist/apps/antenna-pattern/components/PlotlyRadarChart.svelte +0 -22
- package/dist/apps/antenna-pattern/components/chart-engines/PolarAreaChart.svelte +0 -2
- package/dist/apps/antenna-pattern/components/chart-engines/PolarBarChart.svelte +0 -2
- package/dist/apps/antenna-pattern/components/chart-engines/PolarLineChart.svelte +0 -2
- package/dist/apps/site-check/SiteCheck.svelte +60 -80
- package/dist/apps/site-check/data-loader.d.ts +9 -6
- package/dist/apps/site-check/data-loader.js +2 -11
- package/dist/apps/site-check/helper.d.ts +3 -2
- package/dist/apps/site-check/helper.js +7 -5
- package/dist/apps/site-check/index.d.ts +1 -1
- package/dist/apps/site-check/transforms.d.ts +4 -2
- package/dist/apps/site-check/transforms.js +49 -10
- package/dist/core/Charts/GlobalControls.svelte +0 -4
- package/dist/core/Desktop/Grid/ResizeHandle.svelte +0 -7
- package/dist/core/Desktop/Grid/resizeStore.js +0 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/map-v2/demo/DemoMap.svelte +0 -2
- package/dist/map-v2/demo/demo-cells.js +0 -1
- package/dist/map-v2/features/cells/layers/CellsLayer.svelte +7 -26
- package/dist/map-v2/features/cells/utils/cellTree.js +0 -29
- package/dist/map-v2/features/repeaters/layers/RepeaterLabelsLayer.svelte +3 -27
- package/dist/map-v2/features/repeaters/layers/RepeatersLayer.svelte +8 -25
- package/dist/map-v2/features/repeaters/utils/repeaterTree.js +0 -6
- package/dist/map-v2/features/sites/controls/SiteFilterControl.svelte +0 -8
- package/dist/map-v2/features/sites/utils/siteTreeUtils.js +0 -6
- package/dist/map-v3/core/components/Map.svelte +89 -0
- package/dist/map-v3/core/components/Map.svelte.d.ts +13 -0
- package/dist/map-v3/core/controls/FeatureSettingsControl.svelte +103 -0
- package/dist/map-v3/core/controls/FeatureSettingsControl.svelte.d.ts +15 -0
- package/dist/map-v3/core/controls/MapStyleControl.svelte +271 -0
- package/dist/map-v3/core/controls/MapStyleControl.svelte.d.ts +28 -0
- package/dist/map-v3/core/index.d.ts +3 -0
- package/dist/map-v3/core/index.js +3 -0
- package/dist/map-v3/core/stores/map.store.svelte.d.ts +8 -0
- package/dist/map-v3/core/stores/map.store.svelte.js +29 -0
- package/dist/map-v3/core/stores/viewport.store.svelte.d.ts +38 -0
- package/dist/map-v3/core/stores/viewport.store.svelte.js +107 -0
- package/dist/map-v3/demo/DemoMap.svelte +104 -0
- package/dist/map-v3/demo/DemoMap.svelte.d.ts +6 -0
- package/dist/map-v3/demo/demo-cells.d.ts +13 -0
- package/dist/map-v3/demo/demo-cells.js +130 -0
- package/dist/map-v3/demo/demo-data.d.ts +8 -0
- package/dist/map-v3/demo/demo-data.js +104 -0
- package/dist/map-v3/demo/demo-repeaters.d.ts +13 -0
- package/dist/map-v3/demo/demo-repeaters.js +73 -0
- package/dist/map-v3/features/cells/components/CellFilterControl.svelte +208 -0
- package/dist/map-v3/features/cells/components/CellFilterControl.svelte.d.ts +12 -0
- package/dist/map-v3/features/cells/components/CellSettingsPanel.svelte +229 -0
- package/dist/map-v3/features/cells/components/CellSettingsPanel.svelte.d.ts +7 -0
- package/dist/map-v3/features/cells/constants.d.ts +18 -0
- package/dist/map-v3/features/cells/constants.js +37 -0
- package/dist/map-v3/features/cells/layers/CellLabelsLayer.svelte +230 -0
- package/dist/map-v3/features/cells/layers/CellLabelsLayer.svelte.d.ts +11 -0
- package/dist/map-v3/features/cells/layers/CellsLayer.svelte +194 -0
- package/dist/map-v3/features/cells/layers/CellsLayer.svelte.d.ts +11 -0
- package/dist/map-v3/features/cells/layers/index.d.ts +2 -0
- package/dist/map-v3/features/cells/layers/index.js +2 -0
- package/dist/map-v3/features/cells/logic/geometry.d.ts +12 -0
- package/dist/map-v3/features/cells/logic/geometry.js +35 -0
- package/dist/map-v3/features/cells/logic/grouping.d.ts +18 -0
- package/dist/map-v3/features/cells/logic/grouping.js +30 -0
- package/dist/map-v3/features/cells/logic/tree-adapter.d.ts +11 -0
- package/dist/map-v3/features/cells/logic/tree-adapter.js +53 -0
- package/dist/map-v3/features/cells/stores/cell.data.svelte.d.ts +9 -0
- package/dist/map-v3/features/cells/stores/cell.data.svelte.js +16 -0
- package/dist/map-v3/features/cells/stores/cell.display.svelte.d.ts +25 -0
- package/dist/map-v3/features/cells/stores/cell.display.svelte.js +67 -0
- package/dist/map-v3/features/cells/stores/cell.registry.svelte.d.ts +23 -0
- package/dist/map-v3/features/cells/stores/cell.registry.svelte.js +68 -0
- package/dist/map-v3/features/cells/types.d.ts +62 -0
- package/dist/map-v3/features/cells/types.js +6 -0
- package/dist/map-v3/features/repeaters/components/RepeaterFilterControl.svelte +148 -0
- package/dist/map-v3/features/repeaters/components/RepeaterFilterControl.svelte.d.ts +12 -0
- package/dist/map-v3/features/repeaters/components/RepeaterSettingsPanel.svelte +209 -0
- package/dist/map-v3/features/repeaters/components/RepeaterSettingsPanel.svelte.d.ts +7 -0
- package/dist/map-v3/features/repeaters/layers/RepeaterLabelsLayer.svelte +177 -0
- package/dist/map-v3/features/repeaters/layers/RepeaterLabelsLayer.svelte.d.ts +11 -0
- package/dist/map-v3/features/repeaters/layers/RepeatersLayer.svelte +163 -0
- package/dist/map-v3/features/repeaters/layers/RepeatersLayer.svelte.d.ts +11 -0
- package/dist/map-v3/features/repeaters/logic/geometry.d.ts +3 -0
- package/dist/map-v3/features/repeaters/logic/geometry.js +23 -0
- package/dist/map-v3/features/repeaters/logic/grouping.d.ts +8 -0
- package/dist/map-v3/features/repeaters/logic/grouping.js +20 -0
- package/dist/map-v3/features/repeaters/logic/tree-adapter.d.ts +8 -0
- package/dist/map-v3/features/repeaters/logic/tree-adapter.js +43 -0
- package/dist/map-v3/features/repeaters/stores/repeater.data.svelte.d.ts +8 -0
- package/dist/map-v3/features/repeaters/stores/repeater.data.svelte.js +13 -0
- package/dist/map-v3/features/repeaters/stores/repeater.display.svelte.d.ts +21 -0
- package/dist/map-v3/features/repeaters/stores/repeater.display.svelte.js +64 -0
- package/dist/map-v3/features/repeaters/stores/repeater.registry.svelte.d.ts +23 -0
- package/dist/map-v3/features/repeaters/stores/repeater.registry.svelte.js +68 -0
- package/dist/map-v3/features/repeaters/types.d.ts +18 -0
- package/dist/map-v3/features/repeaters/types.js +1 -0
- package/dist/map-v3/features/sites/components/SiteFilterControl.svelte +119 -0
- package/dist/map-v3/features/sites/components/SiteFilterControl.svelte.d.ts +12 -0
- package/dist/map-v3/features/sites/components/SiteSettingsPanel.svelte +241 -0
- package/dist/map-v3/features/sites/components/SiteSettingsPanel.svelte.d.ts +7 -0
- package/dist/map-v3/features/sites/layers/SiteLabelsLayer.svelte +152 -0
- package/dist/map-v3/features/sites/layers/SiteLabelsLayer.svelte.d.ts +11 -0
- package/dist/map-v3/features/sites/layers/SitesLayer.svelte +132 -0
- package/dist/map-v3/features/sites/layers/SitesLayer.svelte.d.ts +11 -0
- package/dist/map-v3/features/sites/logic/tree-adapter.d.ts +9 -0
- package/dist/map-v3/features/sites/logic/tree-adapter.js +75 -0
- package/dist/map-v3/features/sites/stores/site.data.svelte.d.ts +8 -0
- package/dist/map-v3/features/sites/stores/site.data.svelte.js +40 -0
- package/dist/map-v3/features/sites/stores/site.display.svelte.d.ts +20 -0
- package/dist/map-v3/features/sites/stores/site.display.svelte.js +63 -0
- package/dist/map-v3/features/sites/stores/site.registry.svelte.d.ts +13 -0
- package/dist/map-v3/features/sites/stores/site.registry.svelte.js +83 -0
- package/dist/map-v3/features/sites/types.d.ts +12 -0
- package/dist/map-v3/features/sites/types.js +1 -0
- package/dist/map-v3/index.d.ts +26 -0
- package/dist/map-v3/index.js +31 -0
- package/dist/map-v3/shared/controls/MapControl.svelte +242 -0
- package/dist/map-v3/shared/controls/MapControl.svelte.d.ts +27 -0
- package/dist/map-v3/shared/index.d.ts +1 -0
- package/dist/map-v3/shared/index.js +1 -0
- package/package.json +1 -1
|
@@ -45,10 +45,7 @@
|
|
|
45
45
|
let treeStore = $state<Writable<TreeStoreValue> | null>(null);
|
|
46
46
|
|
|
47
47
|
onMount(() => {
|
|
48
|
-
console.log('SiteFilterControl: Mounted with', store.allSites.length, 'sites');
|
|
49
|
-
|
|
50
48
|
if (store.allSites.length > 0) {
|
|
51
|
-
console.log('SiteFilterControl: Building tree...');
|
|
52
49
|
const treeNodes = buildSiteTree(store.allSites, store.groupColorMap);
|
|
53
50
|
|
|
54
51
|
treeStore = createTreeStore({
|
|
@@ -57,17 +54,12 @@
|
|
|
57
54
|
persistState: true,
|
|
58
55
|
defaultExpandAll: false
|
|
59
56
|
});
|
|
60
|
-
|
|
61
|
-
console.log('SiteFilterControl: Tree store created');
|
|
62
57
|
|
|
63
58
|
// Subscribe to tree changes and update siteStore
|
|
64
59
|
if (treeStore) {
|
|
65
60
|
const unsub = treeStore.subscribe((treeValue: TreeStoreValue) => {
|
|
66
61
|
const checkedPaths = treeValue.getCheckedPaths();
|
|
67
|
-
console.log('TreeStore updated, checked paths:', checkedPaths);
|
|
68
|
-
|
|
69
62
|
const newFilteredSites = getFilteredSites(checkedPaths, store.allSites);
|
|
70
|
-
console.log('Filtered sites count:', newFilteredSites.length, 'of', store.allSites.length);
|
|
71
63
|
|
|
72
64
|
// Update the site store directly
|
|
73
65
|
store.filteredSites = newFilteredSites;
|
|
@@ -74,7 +74,6 @@ export function buildSiteTree(sites, colorMap) {
|
|
|
74
74
|
* Filters sites based on checked tree paths
|
|
75
75
|
*/
|
|
76
76
|
export function getFilteredSites(checkedPaths, allSites) {
|
|
77
|
-
console.log('getFilteredSites called with paths:', checkedPaths);
|
|
78
77
|
const checkedGroups = new Set();
|
|
79
78
|
const checkedProviders = new Set();
|
|
80
79
|
checkedPaths.forEach((path) => {
|
|
@@ -97,16 +96,12 @@ export function getFilteredSites(checkedPaths, allSites) {
|
|
|
97
96
|
checkedProviders.add(filteredParts[0]);
|
|
98
97
|
}
|
|
99
98
|
});
|
|
100
|
-
console.log('Checked groups:', Array.from(checkedGroups));
|
|
101
|
-
console.log('Checked providers:', Array.from(checkedProviders));
|
|
102
99
|
// If "all-sites" is checked and it's the only thing, return all
|
|
103
100
|
if (checkedPaths.includes('all-sites') && checkedGroups.size === 0 && checkedProviders.size === 0) {
|
|
104
|
-
console.log('All sites root checked, returning all');
|
|
105
101
|
return allSites;
|
|
106
102
|
}
|
|
107
103
|
// If nothing is checked, return empty
|
|
108
104
|
if (checkedGroups.size === 0 && checkedProviders.size === 0) {
|
|
109
|
-
console.log('Nothing checked, returning empty');
|
|
110
105
|
return [];
|
|
111
106
|
}
|
|
112
107
|
// Filter sites based on checked groups or providers
|
|
@@ -116,6 +111,5 @@ export function getFilteredSites(checkedPaths, allSites) {
|
|
|
116
111
|
const included = checkedGroups.has(key) || checkedProviders.has(site.provider);
|
|
117
112
|
return included;
|
|
118
113
|
});
|
|
119
|
-
console.log('Filtered:', filtered.length, 'of', allSites.length, 'sites');
|
|
120
114
|
return filtered;
|
|
121
115
|
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { setContext, onMount, onDestroy } from 'svelte';
|
|
3
|
+
import mapboxgl from 'mapbox-gl';
|
|
4
|
+
import 'mapbox-gl/dist/mapbox-gl.css';
|
|
5
|
+
import { createMapStore } from '../stores/map.store.svelte';
|
|
6
|
+
import { createViewportStore } from '../stores/viewport.store.svelte';
|
|
7
|
+
|
|
8
|
+
interface Props {
|
|
9
|
+
accessToken: string;
|
|
10
|
+
style?: string;
|
|
11
|
+
center?: [number, number];
|
|
12
|
+
zoom?: number;
|
|
13
|
+
class?: string;
|
|
14
|
+
children?: import('svelte').Snippet;
|
|
15
|
+
namespace?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let {
|
|
19
|
+
accessToken,
|
|
20
|
+
style = 'mapbox://styles/mapbox/streets-v12',
|
|
21
|
+
center = [0, 0],
|
|
22
|
+
zoom = 2,
|
|
23
|
+
class: className = '',
|
|
24
|
+
children,
|
|
25
|
+
namespace = 'map'
|
|
26
|
+
}: Props = $props();
|
|
27
|
+
|
|
28
|
+
let mapContainer: HTMLDivElement;
|
|
29
|
+
const mapStore = createMapStore();
|
|
30
|
+
|
|
31
|
+
// Initialize viewport store with defaults from props
|
|
32
|
+
const viewportStore = createViewportStore(namespace, { center, zoom });
|
|
33
|
+
|
|
34
|
+
setContext('MAP_CONTEXT', mapStore);
|
|
35
|
+
|
|
36
|
+
onMount(() => {
|
|
37
|
+
mapboxgl.accessToken = accessToken;
|
|
38
|
+
|
|
39
|
+
// Use viewport store values for initialization
|
|
40
|
+
mapStore.init(mapContainer, {
|
|
41
|
+
style,
|
|
42
|
+
center: viewportStore.center,
|
|
43
|
+
zoom: viewportStore.zoom,
|
|
44
|
+
bearing: viewportStore.bearing,
|
|
45
|
+
pitch: viewportStore.pitch
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// Sync Map -> Store
|
|
49
|
+
const map = mapStore.map;
|
|
50
|
+
if (map) {
|
|
51
|
+
const updateStore = () => {
|
|
52
|
+
viewportStore.updateViewport({
|
|
53
|
+
center: map.getCenter().toArray() as [number, number],
|
|
54
|
+
zoom: map.getZoom(),
|
|
55
|
+
bearing: map.getBearing(),
|
|
56
|
+
pitch: map.getPitch()
|
|
57
|
+
});
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
map.on('moveend', updateStore);
|
|
61
|
+
map.on('zoomend', updateStore);
|
|
62
|
+
map.on('rotateend', updateStore);
|
|
63
|
+
map.on('pitchend', updateStore);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
onDestroy(() => {
|
|
68
|
+
mapStore.destroy();
|
|
69
|
+
});
|
|
70
|
+
</script>
|
|
71
|
+
|
|
72
|
+
<div class="map-wrapper {className}">
|
|
73
|
+
<div bind:this={mapContainer} class="map-instance"></div>
|
|
74
|
+
{#if mapStore.loaded}
|
|
75
|
+
{@render children?.()}
|
|
76
|
+
{/if}
|
|
77
|
+
</div>
|
|
78
|
+
|
|
79
|
+
<style>
|
|
80
|
+
.map-wrapper {
|
|
81
|
+
width: 100%;
|
|
82
|
+
height: 100%;
|
|
83
|
+
position: relative;
|
|
84
|
+
}
|
|
85
|
+
.map-instance {
|
|
86
|
+
width: 100%;
|
|
87
|
+
height: 100%;
|
|
88
|
+
}
|
|
89
|
+
</style>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import 'mapbox-gl/dist/mapbox-gl.css';
|
|
2
|
+
interface Props {
|
|
3
|
+
accessToken: string;
|
|
4
|
+
style?: string;
|
|
5
|
+
center?: [number, number];
|
|
6
|
+
zoom?: number;
|
|
7
|
+
class?: string;
|
|
8
|
+
children?: import('svelte').Snippet;
|
|
9
|
+
namespace?: string;
|
|
10
|
+
}
|
|
11
|
+
declare const Map: import("svelte").Component<Props, {}, "">;
|
|
12
|
+
type Map = ReturnType<typeof Map>;
|
|
13
|
+
export default Map;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { MapControl } from '../../shared';
|
|
3
|
+
import CellSettingsPanel from '../../features/cells/components/CellSettingsPanel.svelte';
|
|
4
|
+
import RepeaterSettingsPanel from '../../features/repeaters/components/RepeaterSettingsPanel.svelte';
|
|
5
|
+
import SiteSettingsPanel from '../../features/sites/components/SiteSettingsPanel.svelte';
|
|
6
|
+
import type { CellDisplayStore } from '../../features/cells/stores/cell.display.svelte';
|
|
7
|
+
import type { RepeaterDisplayStore } from '../../features/repeaters/stores/repeater.display.svelte';
|
|
8
|
+
import type { SiteDisplayStore } from '../../features/sites/stores/site.display.svelte';
|
|
9
|
+
|
|
10
|
+
interface Props {
|
|
11
|
+
cellDisplayStore: CellDisplayStore;
|
|
12
|
+
repeaterDisplayStore?: RepeaterDisplayStore;
|
|
13
|
+
siteDisplayStore?: SiteDisplayStore;
|
|
14
|
+
position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
|
|
15
|
+
title?: string;
|
|
16
|
+
icon?: string;
|
|
17
|
+
initiallyCollapsed?: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
let {
|
|
21
|
+
cellDisplayStore,
|
|
22
|
+
repeaterDisplayStore,
|
|
23
|
+
siteDisplayStore,
|
|
24
|
+
position = 'top-right',
|
|
25
|
+
title = 'Feature Settings',
|
|
26
|
+
icon = 'sliders',
|
|
27
|
+
initiallyCollapsed = true
|
|
28
|
+
}: Props = $props();
|
|
29
|
+
</script>
|
|
30
|
+
|
|
31
|
+
<MapControl {position} {title} {icon} {initiallyCollapsed} controlWidth="380px" edgeOffset="12px">
|
|
32
|
+
<div class="d-flex flex-column p-0">
|
|
33
|
+
<ul class="nav nav-tabs" id="settings-tabs" role="tablist">
|
|
34
|
+
<!-- Cell Tab -->
|
|
35
|
+
<li class="nav-item" role="presentation">
|
|
36
|
+
<button class="nav-link active" id="cell-tab" data-bs-toggle="tab" data-bs-target="#cell" type="button" role="tab">Cells</button>
|
|
37
|
+
</li>
|
|
38
|
+
<!-- Site Tab -->
|
|
39
|
+
{#if siteDisplayStore}
|
|
40
|
+
<li class="nav-item" role="presentation">
|
|
41
|
+
<button class="nav-link" id="site-tab" data-bs-toggle="tab" data-bs-target="#site" type="button" role="tab">Sites</button>
|
|
42
|
+
</li>
|
|
43
|
+
{/if}
|
|
44
|
+
<!-- Repeater Tab -->
|
|
45
|
+
{#if repeaterDisplayStore}
|
|
46
|
+
<li class="nav-item" role="presentation">
|
|
47
|
+
<button class="nav-link" id="repeater-tab" data-bs-toggle="tab" data-bs-target="#repeater" type="button" role="tab">Repeaters</button>
|
|
48
|
+
</li>
|
|
49
|
+
{/if}
|
|
50
|
+
</ul>
|
|
51
|
+
|
|
52
|
+
<div class="tab-content rounded-2 shadow-sm bg-white border p-2" id="settings-tab-content">
|
|
53
|
+
<!-- Cell Panel -->
|
|
54
|
+
<div class="tab-pane show active" id="cell" role="tabpanel">
|
|
55
|
+
<CellSettingsPanel displayStore={cellDisplayStore} />
|
|
56
|
+
</div>
|
|
57
|
+
<!-- Site Panel -->
|
|
58
|
+
{#if siteDisplayStore}
|
|
59
|
+
<div class="tab-pane" id="site" role="tabpanel">
|
|
60
|
+
<SiteSettingsPanel displayStore={siteDisplayStore} />
|
|
61
|
+
</div>
|
|
62
|
+
{/if}
|
|
63
|
+
<!-- Repeater Panel -->
|
|
64
|
+
{#if repeaterDisplayStore}
|
|
65
|
+
<div class="tab-pane" id="repeater" role="tabpanel">
|
|
66
|
+
<RepeaterSettingsPanel displayStore={repeaterDisplayStore} />
|
|
67
|
+
</div>
|
|
68
|
+
{/if}
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
</MapControl>
|
|
72
|
+
|
|
73
|
+
<style>
|
|
74
|
+
:global(.map-control-content) {
|
|
75
|
+
padding: 0.25rem;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.nav-tabs {
|
|
79
|
+
border-bottom: 1px solid #dee2e6;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.nav-link {
|
|
83
|
+
border: 1px solid transparent;
|
|
84
|
+
border-top-left-radius: 0.375rem;
|
|
85
|
+
border-top-right-radius: 0.375rem;
|
|
86
|
+
font-size: 0.95rem;
|
|
87
|
+
font-weight: 500;
|
|
88
|
+
padding: 0.5rem 1rem;
|
|
89
|
+
color: #6c757d;
|
|
90
|
+
white-space: nowrap;
|
|
91
|
+
min-width: fit-content;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.nav-link.active {
|
|
95
|
+
color: #495057;
|
|
96
|
+
background-color: #fff;
|
|
97
|
+
border-color: #dee2e6 #dee2e6 #fff;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.tab-content {
|
|
101
|
+
padding: 0;
|
|
102
|
+
}
|
|
103
|
+
</style>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { CellDisplayStore } from '../../features/cells/stores/cell.display.svelte';
|
|
2
|
+
import type { RepeaterDisplayStore } from '../../features/repeaters/stores/repeater.display.svelte';
|
|
3
|
+
import type { SiteDisplayStore } from '../../features/sites/stores/site.display.svelte';
|
|
4
|
+
interface Props {
|
|
5
|
+
cellDisplayStore: CellDisplayStore;
|
|
6
|
+
repeaterDisplayStore?: RepeaterDisplayStore;
|
|
7
|
+
siteDisplayStore?: SiteDisplayStore;
|
|
8
|
+
position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
|
|
9
|
+
title?: string;
|
|
10
|
+
icon?: string;
|
|
11
|
+
initiallyCollapsed?: boolean;
|
|
12
|
+
}
|
|
13
|
+
declare const FeatureSettingsControl: import("svelte").Component<Props, {}, "">;
|
|
14
|
+
type FeatureSettingsControl = ReturnType<typeof FeatureSettingsControl>;
|
|
15
|
+
export default FeatureSettingsControl;
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* MapStyleControl - Control for switching map styles
|
|
4
|
+
*
|
|
5
|
+
* Self-contained control that displays available map styles and allows switching.
|
|
6
|
+
* Persists selection to localStorage.
|
|
7
|
+
*/
|
|
8
|
+
import { getContext } from 'svelte';
|
|
9
|
+
import { MapControl } from '../../shared';
|
|
10
|
+
import type { MapStore } from '../stores/map.store.svelte';
|
|
11
|
+
|
|
12
|
+
interface MapStyle {
|
|
13
|
+
id: string;
|
|
14
|
+
name: string;
|
|
15
|
+
url: string;
|
|
16
|
+
description?: string;
|
|
17
|
+
icon?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const DEFAULT_STYLES: MapStyle[] = [
|
|
21
|
+
{
|
|
22
|
+
id: 'streets',
|
|
23
|
+
name: 'Streets',
|
|
24
|
+
url: 'mapbox://styles/mapbox/streets-v12',
|
|
25
|
+
description: 'General purpose street map',
|
|
26
|
+
icon: 'map'
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
id: 'outdoors',
|
|
30
|
+
name: 'Outdoors',
|
|
31
|
+
url: 'mapbox://styles/mapbox/outdoors-v12',
|
|
32
|
+
description: 'Hiking, biking, and terrain',
|
|
33
|
+
icon: 'tree'
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
id: 'light',
|
|
37
|
+
name: 'Light',
|
|
38
|
+
url: 'mapbox://styles/mapbox/light-v11',
|
|
39
|
+
description: 'Minimal light background',
|
|
40
|
+
icon: 'sun'
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
id: 'dark',
|
|
44
|
+
name: 'Dark',
|
|
45
|
+
url: 'mapbox://styles/mapbox/dark-v11',
|
|
46
|
+
description: 'Minimal dark background',
|
|
47
|
+
icon: 'moon-stars'
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
id: 'satellite',
|
|
51
|
+
name: 'Satellite',
|
|
52
|
+
url: 'mapbox://styles/mapbox/satellite-v9',
|
|
53
|
+
description: 'Satellite imagery',
|
|
54
|
+
icon: 'globe'
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
id: 'satellite-streets',
|
|
58
|
+
name: 'Satellite Streets',
|
|
59
|
+
url: 'mapbox://styles/mapbox/satellite-streets-v12',
|
|
60
|
+
description: 'Satellite with street overlay',
|
|
61
|
+
icon: 'globe-americas'
|
|
62
|
+
}
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
interface Props {
|
|
66
|
+
/** Control position on map */
|
|
67
|
+
position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
|
|
68
|
+
/** Control title */
|
|
69
|
+
title?: string;
|
|
70
|
+
/** Optional header icon */
|
|
71
|
+
icon?: string;
|
|
72
|
+
/** Show icon when collapsed (default: true) */
|
|
73
|
+
iconOnlyWhenCollapsed?: boolean;
|
|
74
|
+
/** Initially collapsed? */
|
|
75
|
+
initiallyCollapsed?: boolean;
|
|
76
|
+
/** Storage namespace for persistence */
|
|
77
|
+
namespace?: string;
|
|
78
|
+
/** Custom available styles (default: built-in Mapbox styles) */
|
|
79
|
+
availableStyles?: MapStyle[];
|
|
80
|
+
/** Default style ID */
|
|
81
|
+
defaultStyleId?: string;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
let {
|
|
85
|
+
position = 'top-right',
|
|
86
|
+
title = 'Map Style',
|
|
87
|
+
icon = 'layers',
|
|
88
|
+
iconOnlyWhenCollapsed = true,
|
|
89
|
+
initiallyCollapsed = true,
|
|
90
|
+
namespace = 'map',
|
|
91
|
+
availableStyles = DEFAULT_STYLES,
|
|
92
|
+
defaultStyleId = 'streets'
|
|
93
|
+
}: Props = $props();
|
|
94
|
+
|
|
95
|
+
const mapStore = getContext<MapStore>('MAP_CONTEXT');
|
|
96
|
+
const storageKey = `${namespace}:mapStyle`;
|
|
97
|
+
|
|
98
|
+
// Load persisted style or use default
|
|
99
|
+
const loadPersistedStyle = (): MapStyle => {
|
|
100
|
+
if (typeof window === 'undefined') {
|
|
101
|
+
return availableStyles.find(s => s.id === defaultStyleId) || availableStyles[0];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
try {
|
|
105
|
+
const stored = localStorage.getItem(storageKey);
|
|
106
|
+
if (stored) {
|
|
107
|
+
const styleId = JSON.parse(stored);
|
|
108
|
+
const style = availableStyles.find(s => s.id === styleId);
|
|
109
|
+
if (style) return style;
|
|
110
|
+
}
|
|
111
|
+
} catch (e) {
|
|
112
|
+
console.warn('Failed to load persisted map style:', e);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return availableStyles.find(s => s.id === defaultStyleId) || availableStyles[0];
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
let currentStyle = $state<MapStyle>(loadPersistedStyle());
|
|
119
|
+
|
|
120
|
+
// Apply style when map becomes available
|
|
121
|
+
$effect(() => {
|
|
122
|
+
const map = mapStore.map;
|
|
123
|
+
if (map && currentStyle) {
|
|
124
|
+
const mapStyle = map.getStyle();
|
|
125
|
+
// Compare the style URL/source, not the name
|
|
126
|
+
// mapStyle.sprite contains the base URL we can check against
|
|
127
|
+
const currentMapStyleUrl = mapStyle?.sprite || '';
|
|
128
|
+
// Simple check: if the current style ID isn't in the sprite URL (approximate but usually works for Mapbox styles)
|
|
129
|
+
// Better check: store the last set style URL in a ref if needed, but this logic from V2 is okay for now.
|
|
130
|
+
// Actually, let's just set it if it's the first load or if we are sure.
|
|
131
|
+
// To avoid infinite loops or unnecessary sets, we can check if the map is loaded.
|
|
132
|
+
|
|
133
|
+
// For now, let's trust the user interaction to drive changes,
|
|
134
|
+
// but on initial load, we want to enforce the persisted style.
|
|
135
|
+
// We can't easily read the "URL" back from map.getStyle().
|
|
136
|
+
|
|
137
|
+
// Strategy: We just set it once when the map is first loaded if it differs from default?
|
|
138
|
+
// Or we can just set it. map.setStyle is relatively cheap if it's the same.
|
|
139
|
+
// However, mapbox might reload tiles.
|
|
140
|
+
|
|
141
|
+
// Let's stick to the V2 logic: check if sprite includes the ID.
|
|
142
|
+
const needsStyleChange = !currentMapStyleUrl.includes(currentStyle.id);
|
|
143
|
+
|
|
144
|
+
if (needsStyleChange) {
|
|
145
|
+
// console.log('Applying persisted style:', currentStyle.id);
|
|
146
|
+
map.setStyle(currentStyle.url);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
function handleStyleChange(styleId: string): void {
|
|
152
|
+
const style = availableStyles.find(s => s.id === styleId);
|
|
153
|
+
if (!style) {
|
|
154
|
+
console.warn(`Style with id "${styleId}" not found`);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Update local state
|
|
159
|
+
currentStyle = style;
|
|
160
|
+
|
|
161
|
+
// Persist to localStorage
|
|
162
|
+
if (typeof window !== 'undefined') {
|
|
163
|
+
try {
|
|
164
|
+
localStorage.setItem(storageKey, JSON.stringify(styleId));
|
|
165
|
+
} catch (e) {
|
|
166
|
+
console.warn('Failed to persist map style:', e);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Update map directly
|
|
171
|
+
if (mapStore.map) {
|
|
172
|
+
mapStore.map.setStyle(style.url);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
</script>
|
|
176
|
+
|
|
177
|
+
<MapControl {position} {title} {icon} {iconOnlyWhenCollapsed} collapsible={true} {initiallyCollapsed}>
|
|
178
|
+
<div class="map-style-controls">
|
|
179
|
+
<div class="style-grid">
|
|
180
|
+
{#each availableStyles as style (style.id)}
|
|
181
|
+
<button
|
|
182
|
+
type="button"
|
|
183
|
+
class="btn style-option"
|
|
184
|
+
class:btn-primary={currentStyle.id === style.id}
|
|
185
|
+
class:btn-outline-secondary={currentStyle.id !== style.id}
|
|
186
|
+
class:active={currentStyle.id === style.id}
|
|
187
|
+
onclick={() => handleStyleChange(style.id)}
|
|
188
|
+
title={style.description}
|
|
189
|
+
>
|
|
190
|
+
<i class="bi bi-{style.icon || 'map'} style-icon"></i>
|
|
191
|
+
<span class="style-name">{style.name}</span>
|
|
192
|
+
</button>
|
|
193
|
+
{/each}
|
|
194
|
+
</div>
|
|
195
|
+
</div>
|
|
196
|
+
</MapControl>
|
|
197
|
+
|
|
198
|
+
<style>
|
|
199
|
+
.map-style-controls {
|
|
200
|
+
min-width: 240px;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
.style-grid {
|
|
204
|
+
display: grid;
|
|
205
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
206
|
+
gap: 0.5rem;
|
|
207
|
+
margin-bottom: 0.75rem;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
.style-option {
|
|
211
|
+
display: inline-flex;
|
|
212
|
+
flex-direction: column;
|
|
213
|
+
align-items: center;
|
|
214
|
+
justify-content: center;
|
|
215
|
+
gap: 0.4rem;
|
|
216
|
+
text-align: center;
|
|
217
|
+
font-weight: 500;
|
|
218
|
+
border: var(--bs-btn-border-width, 1px) solid var(
|
|
219
|
+
--bs-btn-border-color,
|
|
220
|
+
var(--bs-border-color, #dee2e6)
|
|
221
|
+
);
|
|
222
|
+
/* Normalise Bootstrap spacing so icons remain centred */
|
|
223
|
+
--bs-btn-padding-y: 0.75rem;
|
|
224
|
+
--bs-btn-padding-x: 0.75rem;
|
|
225
|
+
--bs-btn-line-height: 1.25;
|
|
226
|
+
width: 100%;
|
|
227
|
+
height: auto;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
.style-option .style-icon {
|
|
231
|
+
display: block;
|
|
232
|
+
font-size: 1.2rem;
|
|
233
|
+
line-height: 1;
|
|
234
|
+
margin-top: 0.15rem;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
.style-option .style-name {
|
|
238
|
+
display: block;
|
|
239
|
+
font-size: 0.5rem;
|
|
240
|
+
text-transform: uppercase;
|
|
241
|
+
letter-spacing: 0.04em;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/* Ensure Bootstrap button variables win inside Mapbox control */
|
|
245
|
+
.map-style-controls .btn {
|
|
246
|
+
background-color: var(--bs-btn-bg, transparent);
|
|
247
|
+
border-color: var(--bs-btn-border-color, currentColor);
|
|
248
|
+
color: var(--bs-btn-color, inherit);
|
|
249
|
+
transition: var(--bs-btn-transition, all 0.15s ease);
|
|
250
|
+
border-radius: var(--bs-btn-border-radius, var(--bs-border-radius, 0.375rem));
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
.map-style-controls .btn:hover,
|
|
254
|
+
.map-style-controls .btn:focus {
|
|
255
|
+
background-color: var(--bs-btn-hover-bg, var(--bs-btn-bg, transparent));
|
|
256
|
+
border-color: var(--bs-btn-hover-border-color, var(--bs-btn-border-color, currentColor));
|
|
257
|
+
color: var(--bs-btn-hover-color, var(--bs-btn-color, inherit));
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
.map-style-controls .btn:disabled,
|
|
261
|
+
.map-style-controls .btn:disabled:hover {
|
|
262
|
+
background-color: var(--bs-btn-disabled-bg, var(--bs-btn-bg, transparent));
|
|
263
|
+
border-color: var(--bs-btn-disabled-border-color, var(--bs-btn-border-color, currentColor));
|
|
264
|
+
color: var(--bs-btn-disabled-color, var(--bs-btn-color, inherit));
|
|
265
|
+
opacity: var(--bs-btn-disabled-opacity, 0.65);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
.map-style-controls .btn-primary {
|
|
269
|
+
box-shadow: 0 4px 12px rgba(var(--bs-primary-rgb, 13, 110, 253), 0.18);
|
|
270
|
+
}
|
|
271
|
+
</style>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
interface MapStyle {
|
|
2
|
+
id: string;
|
|
3
|
+
name: string;
|
|
4
|
+
url: string;
|
|
5
|
+
description?: string;
|
|
6
|
+
icon?: string;
|
|
7
|
+
}
|
|
8
|
+
interface Props {
|
|
9
|
+
/** Control position on map */
|
|
10
|
+
position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
|
|
11
|
+
/** Control title */
|
|
12
|
+
title?: string;
|
|
13
|
+
/** Optional header icon */
|
|
14
|
+
icon?: string;
|
|
15
|
+
/** Show icon when collapsed (default: true) */
|
|
16
|
+
iconOnlyWhenCollapsed?: boolean;
|
|
17
|
+
/** Initially collapsed? */
|
|
18
|
+
initiallyCollapsed?: boolean;
|
|
19
|
+
/** Storage namespace for persistence */
|
|
20
|
+
namespace?: string;
|
|
21
|
+
/** Custom available styles (default: built-in Mapbox styles) */
|
|
22
|
+
availableStyles?: MapStyle[];
|
|
23
|
+
/** Default style ID */
|
|
24
|
+
defaultStyleId?: string;
|
|
25
|
+
}
|
|
26
|
+
declare const MapStyleControl: import("svelte").Component<Props, {}, "">;
|
|
27
|
+
type MapStyleControl = ReturnType<typeof MapStyleControl>;
|
|
28
|
+
export default MapStyleControl;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare class MapStore {
|
|
2
|
+
map: import("mapbox-gl").Map | null;
|
|
3
|
+
loaded: boolean;
|
|
4
|
+
constructor();
|
|
5
|
+
init(container: HTMLElement, options: Omit<mapboxgl.MapboxOptions, 'container'>): void;
|
|
6
|
+
destroy(): void;
|
|
7
|
+
}
|
|
8
|
+
export declare function createMapStore(): MapStore;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import mapboxgl from 'mapbox-gl';
|
|
2
|
+
export class MapStore {
|
|
3
|
+
map = $state(null);
|
|
4
|
+
loaded = $state(false);
|
|
5
|
+
constructor() {
|
|
6
|
+
// No-op
|
|
7
|
+
}
|
|
8
|
+
init(container, options) {
|
|
9
|
+
if (this.map)
|
|
10
|
+
return;
|
|
11
|
+
this.map = new mapboxgl.Map({
|
|
12
|
+
container,
|
|
13
|
+
...options
|
|
14
|
+
});
|
|
15
|
+
this.map.on('load', () => {
|
|
16
|
+
this.loaded = true;
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
destroy() {
|
|
20
|
+
if (this.map) {
|
|
21
|
+
this.map.remove();
|
|
22
|
+
this.map = null;
|
|
23
|
+
this.loaded = false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export function createMapStore() {
|
|
28
|
+
return new MapStore();
|
|
29
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Viewport Store - Manages and persists map viewport state (center, zoom, pitch, bearing)
|
|
3
|
+
*
|
|
4
|
+
* Features:
|
|
5
|
+
* - Reactive state using Svelte 5 runes
|
|
6
|
+
* - Auto-persists to localStorage via $effect
|
|
7
|
+
* - Namespace isolation for multiple map instances
|
|
8
|
+
*/
|
|
9
|
+
export interface ViewportState {
|
|
10
|
+
/** Map center [lng, lat] */
|
|
11
|
+
center: [number, number];
|
|
12
|
+
/** Zoom level */
|
|
13
|
+
zoom: number;
|
|
14
|
+
/** Map bearing (rotation) in degrees */
|
|
15
|
+
bearing: number;
|
|
16
|
+
/** Map pitch (tilt) in degrees */
|
|
17
|
+
pitch: number;
|
|
18
|
+
}
|
|
19
|
+
export interface ViewportStore {
|
|
20
|
+
readonly center: [number, number];
|
|
21
|
+
readonly zoom: number;
|
|
22
|
+
readonly bearing: number;
|
|
23
|
+
readonly pitch: number;
|
|
24
|
+
setCenter(center: [number, number]): void;
|
|
25
|
+
setZoom(zoom: number): void;
|
|
26
|
+
setBearing(bearing: number): void;
|
|
27
|
+
setPitch(pitch: number): void;
|
|
28
|
+
updateViewport(state: Partial<ViewportState>): void;
|
|
29
|
+
reset(): void;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Create a viewport store with persistence
|
|
33
|
+
*
|
|
34
|
+
* @param namespace - Unique identifier for localStorage key
|
|
35
|
+
* @param defaults - Default viewport values (used if no saved state exists)
|
|
36
|
+
* @returns ViewportStore instance
|
|
37
|
+
*/
|
|
38
|
+
export declare function createViewportStore(namespace: string, defaults?: Partial<ViewportState>): ViewportStore;
|