@smartnet360/svelte-components 0.0.85 → 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/data-loader.js +0 -8
- 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
|
@@ -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;
|
|
@@ -0,0 +1,107 @@
|
|
|
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
|
+
const DEFAULT_STATE = {
|
|
10
|
+
center: [0, 0],
|
|
11
|
+
zoom: 2,
|
|
12
|
+
bearing: 0,
|
|
13
|
+
pitch: 0
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Load viewport state from localStorage
|
|
17
|
+
*/
|
|
18
|
+
function loadViewportState(namespace, defaults) {
|
|
19
|
+
if (typeof window === 'undefined') {
|
|
20
|
+
return { ...DEFAULT_STATE, ...defaults };
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
const key = `${namespace}:viewport`;
|
|
24
|
+
const stored = localStorage.getItem(key);
|
|
25
|
+
if (stored) {
|
|
26
|
+
const parsed = JSON.parse(stored);
|
|
27
|
+
return {
|
|
28
|
+
center: parsed.center || defaults.center || DEFAULT_STATE.center,
|
|
29
|
+
zoom: parsed.zoom ?? defaults.zoom ?? DEFAULT_STATE.zoom,
|
|
30
|
+
bearing: parsed.bearing ?? defaults.bearing ?? DEFAULT_STATE.bearing,
|
|
31
|
+
pitch: parsed.pitch ?? defaults.pitch ?? DEFAULT_STATE.pitch
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
console.warn('Failed to load viewport state:', error);
|
|
37
|
+
}
|
|
38
|
+
return { ...DEFAULT_STATE, ...defaults };
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Save viewport state to localStorage
|
|
42
|
+
*/
|
|
43
|
+
function saveViewportState(namespace, state) {
|
|
44
|
+
if (typeof window === 'undefined')
|
|
45
|
+
return;
|
|
46
|
+
try {
|
|
47
|
+
const key = `${namespace}:viewport`;
|
|
48
|
+
localStorage.setItem(key, JSON.stringify(state));
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
console.warn('Failed to save viewport state:', error);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Create a viewport store with persistence
|
|
56
|
+
*
|
|
57
|
+
* @param namespace - Unique identifier for localStorage key
|
|
58
|
+
* @param defaults - Default viewport values (used if no saved state exists)
|
|
59
|
+
* @returns ViewportStore instance
|
|
60
|
+
*/
|
|
61
|
+
export function createViewportStore(namespace, defaults = {}) {
|
|
62
|
+
// Load initial state
|
|
63
|
+
let state = $state(loadViewportState(namespace, defaults));
|
|
64
|
+
// Auto-save to localStorage on any change
|
|
65
|
+
$effect(() => {
|
|
66
|
+
saveViewportState(namespace, state);
|
|
67
|
+
});
|
|
68
|
+
return {
|
|
69
|
+
// Getters
|
|
70
|
+
get center() { return state.center; },
|
|
71
|
+
get zoom() { return state.zoom; },
|
|
72
|
+
get bearing() { return state.bearing; },
|
|
73
|
+
get pitch() { return state.pitch; },
|
|
74
|
+
// Setters
|
|
75
|
+
setCenter(center) {
|
|
76
|
+
state.center = center;
|
|
77
|
+
},
|
|
78
|
+
setZoom(zoom) {
|
|
79
|
+
state.zoom = zoom;
|
|
80
|
+
},
|
|
81
|
+
setBearing(bearing) {
|
|
82
|
+
state.bearing = bearing;
|
|
83
|
+
},
|
|
84
|
+
setPitch(pitch) {
|
|
85
|
+
state.pitch = pitch;
|
|
86
|
+
},
|
|
87
|
+
// Bulk update (more efficient than individual setters)
|
|
88
|
+
updateViewport(updates) {
|
|
89
|
+
if (updates.center !== undefined)
|
|
90
|
+
state.center = updates.center;
|
|
91
|
+
if (updates.zoom !== undefined)
|
|
92
|
+
state.zoom = updates.zoom;
|
|
93
|
+
if (updates.bearing !== undefined)
|
|
94
|
+
state.bearing = updates.bearing;
|
|
95
|
+
if (updates.pitch !== undefined)
|
|
96
|
+
state.pitch = updates.pitch;
|
|
97
|
+
},
|
|
98
|
+
// Reset to defaults
|
|
99
|
+
reset() {
|
|
100
|
+
const resetState = { ...DEFAULT_STATE, ...defaults };
|
|
101
|
+
state.center = resetState.center;
|
|
102
|
+
state.zoom = resetState.zoom;
|
|
103
|
+
state.bearing = resetState.bearing;
|
|
104
|
+
state.pitch = resetState.pitch;
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { onMount } from 'svelte';
|
|
3
|
+
import { Map, MapStyleControl } from '../core';
|
|
4
|
+
import { CellsLayer, CellLabelsLayer } from '../features/cells/layers';
|
|
5
|
+
import { createCellDataStore } from '../features/cells/stores/cell.data.svelte';
|
|
6
|
+
import { createCellRegistry } from '../features/cells/stores/cell.registry.svelte';
|
|
7
|
+
import { CellDisplayStore } from '../features/cells/stores/cell.display.svelte';
|
|
8
|
+
import FeatureSettingsControl from '../core/controls/FeatureSettingsControl.svelte';
|
|
9
|
+
import CellFilterControl from '../features/cells/components/CellFilterControl.svelte';
|
|
10
|
+
import { createRepeaterDataStore } from '../features/repeaters/stores/repeater.data.svelte';
|
|
11
|
+
import { createRepeaterRegistry } from '../features/repeaters/stores/repeater.registry.svelte';
|
|
12
|
+
import { RepeaterDisplayStore } from '../features/repeaters/stores/repeater.display.svelte';
|
|
13
|
+
import RepeaterFilterControl from '../features/repeaters/components/RepeaterFilterControl.svelte';
|
|
14
|
+
import RepeatersLayer from '../features/repeaters/layers/RepeatersLayer.svelte';
|
|
15
|
+
import RepeaterLabelsLayer from '../features/repeaters/layers/RepeaterLabelsLayer.svelte';
|
|
16
|
+
import { createSiteDataStore } from '../features/sites/stores/site.data.svelte';
|
|
17
|
+
import { createSiteDisplayStore } from '../features/sites/stores/site.display.svelte';
|
|
18
|
+
import SitesLayer from '../features/sites/layers/SitesLayer.svelte';
|
|
19
|
+
import SiteLabelsLayer from '../features/sites/layers/SiteLabelsLayer.svelte';
|
|
20
|
+
import SiteFilterControl from '../features/sites/components/SiteFilterControl.svelte';
|
|
21
|
+
import { createSiteRegistry } from '../features/sites/stores/site.registry.svelte';
|
|
22
|
+
import { demoCells } from './demo-cells';
|
|
23
|
+
import { demoRepeaters } from './demo-repeaters';
|
|
24
|
+
|
|
25
|
+
interface Props {
|
|
26
|
+
accessToken: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
let { accessToken }: Props = $props();
|
|
30
|
+
|
|
31
|
+
// Initialize stores
|
|
32
|
+
const cellData = createCellDataStore();
|
|
33
|
+
const cellRegistry = createCellRegistry('demo-map');
|
|
34
|
+
const cellDisplay = new CellDisplayStore();
|
|
35
|
+
|
|
36
|
+
const siteData = createSiteDataStore(cellData);
|
|
37
|
+
const siteRegistry = createSiteRegistry('demo-map');
|
|
38
|
+
const siteDisplay = createSiteDisplayStore();
|
|
39
|
+
|
|
40
|
+
const repeaterData = createRepeaterDataStore();
|
|
41
|
+
const repeaterRegistry = createRepeaterRegistry('demo-map');
|
|
42
|
+
const repeaterDisplay = new RepeaterDisplayStore();
|
|
43
|
+
|
|
44
|
+
onMount(() => {
|
|
45
|
+
// Load dummy data
|
|
46
|
+
// Need to cast or map if types slightly differ, but they should match
|
|
47
|
+
cellData.setCells(demoCells as any);
|
|
48
|
+
repeaterData.setRepeaters(demoRepeaters);
|
|
49
|
+
});
|
|
50
|
+
</script>
|
|
51
|
+
|
|
52
|
+
<div class="map-wrapper">
|
|
53
|
+
<Map
|
|
54
|
+
|
|
55
|
+
{accessToken}
|
|
56
|
+
center={[19.0402, 47.4979]}
|
|
57
|
+
zoom={13}
|
|
58
|
+
namespace="demo-map"
|
|
59
|
+
>
|
|
60
|
+
<MapStyleControl position="top-right" namespace="demo-map" />
|
|
61
|
+
|
|
62
|
+
<CellFilterControl
|
|
63
|
+
position="top-left"
|
|
64
|
+
dataStore={cellData}
|
|
65
|
+
registry={cellRegistry}
|
|
66
|
+
displayStore={cellDisplay}
|
|
67
|
+
/>
|
|
68
|
+
|
|
69
|
+
<SiteFilterControl
|
|
70
|
+
position="top-left"
|
|
71
|
+
dataStore={siteData}
|
|
72
|
+
registry={siteRegistry}
|
|
73
|
+
displayStore={siteDisplay}
|
|
74
|
+
/>
|
|
75
|
+
|
|
76
|
+
<RepeaterFilterControl
|
|
77
|
+
position="top-left"
|
|
78
|
+
dataStore={repeaterData}
|
|
79
|
+
registry={repeaterRegistry}
|
|
80
|
+
displayStore={repeaterDisplay}
|
|
81
|
+
/>
|
|
82
|
+
|
|
83
|
+
<FeatureSettingsControl
|
|
84
|
+
position="top-right"
|
|
85
|
+
cellDisplayStore={cellDisplay}
|
|
86
|
+
repeaterDisplayStore={repeaterDisplay}
|
|
87
|
+
siteDisplayStore={siteDisplay}
|
|
88
|
+
/>
|
|
89
|
+
|
|
90
|
+
<SitesLayer dataStore={siteData} displayStore={siteDisplay} registry={siteRegistry} />
|
|
91
|
+
<SiteLabelsLayer dataStore={siteData} displayStore={siteDisplay} registry={siteRegistry} />
|
|
92
|
+
<CellsLayer dataStore={cellData} registry={cellRegistry} displayStore={cellDisplay} />
|
|
93
|
+
<CellLabelsLayer dataStore={cellData} registry={cellRegistry} displayStore={cellDisplay} />
|
|
94
|
+
<RepeatersLayer dataStore={repeaterData} registry={repeaterRegistry} displayStore={repeaterDisplay} />
|
|
95
|
+
<RepeaterLabelsLayer dataStore={repeaterData} registry={repeaterRegistry} displayStore={repeaterDisplay} />
|
|
96
|
+
</Map>
|
|
97
|
+
</div>
|
|
98
|
+
|
|
99
|
+
<style>
|
|
100
|
+
.map-wrapper {
|
|
101
|
+
width: 100%;
|
|
102
|
+
height: 100%;
|
|
103
|
+
}
|
|
104
|
+
</style>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Demo Cell Data
|
|
3
|
+
*
|
|
4
|
+
* 100 sites across San Francisco Bay Area
|
|
5
|
+
* Each site has 3 sectors (azimuths: 0°, 120°, 240°)
|
|
6
|
+
* Each sector has 12 cells (all tech-band combinations)
|
|
7
|
+
* Total: 100 sites × 3 sectors × 12 tech-bands = 3,600 cells
|
|
8
|
+
*/
|
|
9
|
+
import type { Cell } from '../features/cells/types';
|
|
10
|
+
/**
|
|
11
|
+
* Generate demo cells: 100 sites × 3 sectors × 12 tech-bands = 3,600 cells
|
|
12
|
+
*/
|
|
13
|
+
export declare const demoCells: Cell[];
|