@smartnet360/svelte-components 0.0.51 → 0.0.54
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/site-check/transforms.js +2 -2
- package/dist/core/Charts/ChartCard.svelte +6 -1
- package/dist/core/Charts/ChartComponent.svelte +8 -3
- package/dist/core/Charts/GlobalControls.svelte +116 -14
- package/dist/core/Charts/adapt.js +1 -1
- package/dist/core/Charts/charts.model.d.ts +3 -0
- package/dist/core/FeatureRegistry/index.js +1 -1
- package/dist/core/TreeView/TreeNode.svelte +40 -45
- package/dist/core/TreeView/TreeNode.svelte.d.ts +10 -0
- package/dist/core/TreeView/TreeView.svelte +14 -2
- package/dist/core/TreeView/TreeView.svelte.d.ts +10 -0
- package/dist/core/TreeView/tree-utils.d.ts +3 -0
- package/dist/core/TreeView/tree-utils.js +33 -9
- package/dist/core/TreeView/tree.store.js +49 -24
- package/dist/core/index.d.ts +0 -1
- package/dist/core/index.js +2 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/map/controls/MapControl.svelte +204 -0
- package/dist/map/controls/MapControl.svelte.d.ts +17 -0
- package/dist/map/controls/SiteFilterControl.svelte +126 -0
- package/dist/map/controls/SiteFilterControl.svelte.d.ts +16 -0
- package/dist/map/demo/DemoMap.svelte +98 -0
- package/dist/map/demo/DemoMap.svelte.d.ts +12 -0
- package/dist/map/demo/demo-data.d.ts +12 -0
- package/dist/map/demo/demo-data.js +220 -0
- package/dist/map/hooks/useCellData.d.ts +14 -0
- package/dist/map/hooks/useCellData.js +29 -0
- package/dist/map/hooks/useMapbox.d.ts +14 -0
- package/dist/map/hooks/useMapbox.js +29 -0
- package/dist/map/index.d.ts +27 -0
- package/dist/map/index.js +47 -0
- package/dist/map/layers/CellsLayer.svelte +242 -0
- package/dist/map/layers/CellsLayer.svelte.d.ts +21 -0
- package/dist/map/layers/CoverageLayer.svelte +37 -0
- package/dist/map/layers/CoverageLayer.svelte.d.ts +9 -0
- package/dist/map/layers/LayerBase.d.ts +42 -0
- package/dist/map/layers/LayerBase.js +58 -0
- package/dist/map/layers/SitesLayer.svelte +282 -0
- package/dist/map/layers/SitesLayer.svelte.d.ts +19 -0
- package/dist/map/providers/CellDataProvider.svelte +43 -0
- package/dist/map/providers/CellDataProvider.svelte.d.ts +12 -0
- package/dist/map/providers/MapboxProvider.svelte +38 -0
- package/dist/map/providers/MapboxProvider.svelte.d.ts +9 -0
- package/dist/map/providers/providerHelpers.d.ts +17 -0
- package/dist/map/providers/providerHelpers.js +26 -0
- package/dist/map/stores/cellDataStore.d.ts +21 -0
- package/dist/map/stores/cellDataStore.js +53 -0
- package/dist/map/stores/interactions.d.ts +20 -0
- package/dist/map/stores/interactions.js +33 -0
- package/dist/map/stores/mapStore.d.ts +8 -0
- package/dist/map/stores/mapStore.js +10 -0
- package/dist/map/types.d.ts +115 -0
- package/dist/map/types.js +10 -0
- package/dist/map/utils/geojson.d.ts +20 -0
- package/dist/map/utils/geojson.js +78 -0
- package/dist/map/utils/mapboxHelpers.d.ts +51 -0
- package/dist/map/utils/mapboxHelpers.js +98 -0
- package/dist/map/utils/math.d.ts +40 -0
- package/dist/map/utils/math.js +95 -0
- package/dist/map/utils/siteTreeUtils.d.ts +27 -0
- package/dist/map/utils/siteTreeUtils.js +164 -0
- package/package.json +1 -1
- package/dist/core/Map/Map.svelte +0 -312
- package/dist/core/Map/Map.svelte.d.ts +0 -230
- package/dist/core/Map/index.d.ts +0 -9
- package/dist/core/Map/index.js +0 -9
- package/dist/core/Map/mapSettings.d.ts +0 -147
- package/dist/core/Map/mapSettings.js +0 -226
- package/dist/core/Map/mapStore.d.ts +0 -73
- package/dist/core/Map/mapStore.js +0 -136
- package/dist/core/Map/types.d.ts +0 -72
- package/dist/core/Map/types.js +0 -32
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared utilities for Mapbox layer components
|
|
3
|
+
*/
|
|
4
|
+
export interface LayerConfig {
|
|
5
|
+
namespace: string;
|
|
6
|
+
layerName: string;
|
|
7
|
+
sourceName: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Base class for managing Mapbox layers with consistent patterns
|
|
11
|
+
*/
|
|
12
|
+
export declare class LayerBase {
|
|
13
|
+
protected map: mapboxgl.Map;
|
|
14
|
+
protected config: LayerConfig;
|
|
15
|
+
protected layerId: string;
|
|
16
|
+
protected sourceId: string;
|
|
17
|
+
constructor(map: mapboxgl.Map, config: LayerConfig);
|
|
18
|
+
/**
|
|
19
|
+
* Adds a GeoJSON source to the map
|
|
20
|
+
*/
|
|
21
|
+
protected addGeoJSONSource(data: GeoJSON.FeatureCollection | GeoJSON.Feature): void;
|
|
22
|
+
/**
|
|
23
|
+
* Adds a layer to the map
|
|
24
|
+
*/
|
|
25
|
+
protected addLayer(layer: mapboxgl.AnyLayer, beforeId?: string): void;
|
|
26
|
+
/**
|
|
27
|
+
* Updates the GeoJSON source data
|
|
28
|
+
*/
|
|
29
|
+
protected updateSource(data: GeoJSON.FeatureCollection | GeoJSON.Feature): void;
|
|
30
|
+
/**
|
|
31
|
+
* Removes the layer and source from the map
|
|
32
|
+
*/
|
|
33
|
+
cleanup(): void;
|
|
34
|
+
/**
|
|
35
|
+
* Gets the layer ID
|
|
36
|
+
*/
|
|
37
|
+
getLayerId(): string;
|
|
38
|
+
/**
|
|
39
|
+
* Gets the source ID
|
|
40
|
+
*/
|
|
41
|
+
getSourceId(): string;
|
|
42
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared utilities for Mapbox layer components
|
|
3
|
+
*/
|
|
4
|
+
import { addSourceIfMissing, addLayerIfMissing, removeLayerAndSource, generateLayerId, generateSourceId, updateGeoJSONSource } from '../utils/mapboxHelpers';
|
|
5
|
+
/**
|
|
6
|
+
* Base class for managing Mapbox layers with consistent patterns
|
|
7
|
+
*/
|
|
8
|
+
export class LayerBase {
|
|
9
|
+
map;
|
|
10
|
+
config;
|
|
11
|
+
layerId;
|
|
12
|
+
sourceId;
|
|
13
|
+
constructor(map, config) {
|
|
14
|
+
this.map = map;
|
|
15
|
+
this.config = config;
|
|
16
|
+
this.layerId = generateLayerId(config.namespace, config.layerName);
|
|
17
|
+
this.sourceId = generateSourceId(config.namespace, config.sourceName);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Adds a GeoJSON source to the map
|
|
21
|
+
*/
|
|
22
|
+
addGeoJSONSource(data) {
|
|
23
|
+
addSourceIfMissing(this.map, this.sourceId, {
|
|
24
|
+
type: 'geojson',
|
|
25
|
+
data
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Adds a layer to the map
|
|
30
|
+
*/
|
|
31
|
+
addLayer(layer, beforeId) {
|
|
32
|
+
addLayerIfMissing(this.map, layer, beforeId);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Updates the GeoJSON source data
|
|
36
|
+
*/
|
|
37
|
+
updateSource(data) {
|
|
38
|
+
updateGeoJSONSource(this.map, this.sourceId, data);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Removes the layer and source from the map
|
|
42
|
+
*/
|
|
43
|
+
cleanup() {
|
|
44
|
+
removeLayerAndSource(this.map, this.layerId, this.sourceId);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Gets the layer ID
|
|
48
|
+
*/
|
|
49
|
+
getLayerId() {
|
|
50
|
+
return this.layerId;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Gets the source ID
|
|
54
|
+
*/
|
|
55
|
+
getSourceId() {
|
|
56
|
+
return this.sourceId;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* SitesLayer - Visualizes cellular sites as points on the map
|
|
4
|
+
*
|
|
5
|
+
* Consumes:
|
|
6
|
+
* - Map instance from MapboxProvider
|
|
7
|
+
* - Sites data from CellDataProvider
|
|
8
|
+
*
|
|
9
|
+
* Features:
|
|
10
|
+
* - Displays sites as circles
|
|
11
|
+
* - Supports selection and hover states
|
|
12
|
+
* - Click to select a site
|
|
13
|
+
*/
|
|
14
|
+
import { onMount, onDestroy } from 'svelte';
|
|
15
|
+
import mapboxgl from 'mapbox-gl';
|
|
16
|
+
import { useMapbox } from '../hooks/useMapbox';
|
|
17
|
+
import { useCellData } from '../hooks/useCellData';
|
|
18
|
+
import { sitesToGeoJSON } from '../utils/geojson';
|
|
19
|
+
import { LayerBase } from './LayerBase';
|
|
20
|
+
import type { Site } from '../types';
|
|
21
|
+
|
|
22
|
+
interface Props {
|
|
23
|
+
/** Namespace for layer IDs (default: 'cellular') */
|
|
24
|
+
namespace?: string;
|
|
25
|
+
/** Enable click to select sites (default: true) */
|
|
26
|
+
clickable?: boolean;
|
|
27
|
+
/** Show popup on click (default: true) */
|
|
28
|
+
showPopup?: boolean;
|
|
29
|
+
/** Site circle radius (default: 8) */
|
|
30
|
+
circleRadius?: number;
|
|
31
|
+
/** Site circle color (default: '#3b82f6') */
|
|
32
|
+
circleColor?: string;
|
|
33
|
+
/** Selected site color (default: '#ef4444') */
|
|
34
|
+
selectedColor?: string;
|
|
35
|
+
/** Hovered site color (default: '#8b5cf6') */
|
|
36
|
+
hoverColor?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
let {
|
|
40
|
+
namespace = 'cellular',
|
|
41
|
+
clickable = true,
|
|
42
|
+
showPopup = true,
|
|
43
|
+
circleRadius = 8,
|
|
44
|
+
circleColor = '#3b82f6',
|
|
45
|
+
selectedColor = '#ef4444',
|
|
46
|
+
hoverColor = '#8b5cf6'
|
|
47
|
+
}: Props = $props();
|
|
48
|
+
|
|
49
|
+
const mapStore = useMapbox();
|
|
50
|
+
const cellDataContext = useCellData();
|
|
51
|
+
|
|
52
|
+
let map: mapboxgl.Map | null = null;
|
|
53
|
+
let layerManager: LayerBase | null = null;
|
|
54
|
+
let popup: mapboxgl.Popup | null = null;
|
|
55
|
+
let unsubscribers: (() => void)[] = [];
|
|
56
|
+
|
|
57
|
+
onMount(() => {
|
|
58
|
+
// Subscribe to map store
|
|
59
|
+
const mapUnsub = mapStore.subscribe((m) => {
|
|
60
|
+
if (!m) return;
|
|
61
|
+
map = m;
|
|
62
|
+
initializeLayer();
|
|
63
|
+
});
|
|
64
|
+
unsubscribers.push(mapUnsub);
|
|
65
|
+
|
|
66
|
+
// Subscribe to sites data
|
|
67
|
+
const sitesUnsub = cellDataContext.sites.subscribe((sites) => {
|
|
68
|
+
if (map && layerManager) {
|
|
69
|
+
updateLayerData(sites);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
unsubscribers.push(sitesUnsub);
|
|
73
|
+
|
|
74
|
+
// Subscribe to selection state
|
|
75
|
+
const selectionUnsub = cellDataContext.selectedSiteId.subscribe(() => {
|
|
76
|
+
if (map) {
|
|
77
|
+
updateLayerStyle();
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
unsubscribers.push(selectionUnsub);
|
|
81
|
+
|
|
82
|
+
// Subscribe to hover state
|
|
83
|
+
const hoverUnsub = cellDataContext.hoveredSiteId.subscribe(() => {
|
|
84
|
+
if (map) {
|
|
85
|
+
updateLayerStyle();
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
unsubscribers.push(hoverUnsub);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
onDestroy(() => {
|
|
92
|
+
cleanup();
|
|
93
|
+
unsubscribers.forEach((unsub) => unsub());
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
function initializeLayer(): void {
|
|
97
|
+
if (!map) return;
|
|
98
|
+
|
|
99
|
+
layerManager = new LayerBase(map, {
|
|
100
|
+
namespace,
|
|
101
|
+
layerName: 'sites',
|
|
102
|
+
sourceName: 'sites'
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// Add empty GeoJSON source
|
|
106
|
+
layerManager['addGeoJSONSource']({
|
|
107
|
+
type: 'FeatureCollection',
|
|
108
|
+
features: []
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// Add circle layer
|
|
112
|
+
layerManager['addLayer']({
|
|
113
|
+
id: layerManager.getLayerId(),
|
|
114
|
+
type: 'circle',
|
|
115
|
+
source: layerManager.getSourceId(),
|
|
116
|
+
paint: {
|
|
117
|
+
'circle-radius': circleRadius,
|
|
118
|
+
'circle-color': [
|
|
119
|
+
'case',
|
|
120
|
+
['boolean', ['feature-state', 'selected'], false],
|
|
121
|
+
selectedColor,
|
|
122
|
+
['boolean', ['feature-state', 'hovered'], false],
|
|
123
|
+
hoverColor,
|
|
124
|
+
circleColor
|
|
125
|
+
],
|
|
126
|
+
'circle-stroke-width': 2,
|
|
127
|
+
'circle-stroke-color': '#ffffff'
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// Add click handler
|
|
132
|
+
if (clickable) {
|
|
133
|
+
map.on('click', layerManager.getLayerId(), handleClick);
|
|
134
|
+
map.on('mouseenter', layerManager.getLayerId(), handleMouseEnter);
|
|
135
|
+
map.on('mouseleave', layerManager.getLayerId(), handleMouseLeave);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Initial data load
|
|
139
|
+
const sites = cellDataContext.sites;
|
|
140
|
+
let currentSites: Site[] = [];
|
|
141
|
+
sites.subscribe((s) => {
|
|
142
|
+
currentSites = s;
|
|
143
|
+
})();
|
|
144
|
+
updateLayerData(currentSites);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function updateLayerData(sites: Site[]): void {
|
|
148
|
+
if (!layerManager) return;
|
|
149
|
+
const geojson = sitesToGeoJSON(sites);
|
|
150
|
+
layerManager['updateSource'](geojson);
|
|
151
|
+
updateLayerStyle();
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function updateLayerStyle(): void {
|
|
155
|
+
if (!map || !layerManager) return;
|
|
156
|
+
|
|
157
|
+
const selectedId = cellDataContext.selectedSiteId;
|
|
158
|
+
const hoveredId = cellDataContext.hoveredSiteId;
|
|
159
|
+
|
|
160
|
+
let currentSelectedId: string | null = null;
|
|
161
|
+
let currentHoveredId: string | null = null;
|
|
162
|
+
|
|
163
|
+
selectedId.subscribe((id) => {
|
|
164
|
+
currentSelectedId = id;
|
|
165
|
+
})();
|
|
166
|
+
hoveredId.subscribe((id) => {
|
|
167
|
+
currentHoveredId = id;
|
|
168
|
+
})();
|
|
169
|
+
|
|
170
|
+
// Update feature states
|
|
171
|
+
const sites = cellDataContext.sites;
|
|
172
|
+
let currentSites: Site[] = [];
|
|
173
|
+
sites.subscribe((s) => {
|
|
174
|
+
currentSites = s;
|
|
175
|
+
})();
|
|
176
|
+
|
|
177
|
+
currentSites.forEach((site) => {
|
|
178
|
+
if (map) {
|
|
179
|
+
map.setFeatureState(
|
|
180
|
+
{
|
|
181
|
+
source: layerManager!.getSourceId(),
|
|
182
|
+
id: site.id
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
selected: site.id === currentSelectedId,
|
|
186
|
+
hovered: site.id === currentHoveredId
|
|
187
|
+
}
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function handleClick(e: mapboxgl.MapLayerMouseEvent): void {
|
|
194
|
+
if (!e.features || e.features.length === 0) return;
|
|
195
|
+
const feature = e.features[0];
|
|
196
|
+
const siteId = feature.properties?.id;
|
|
197
|
+
if (siteId) {
|
|
198
|
+
cellDataContext.selectedSiteId.set(siteId);
|
|
199
|
+
cellDataContext.selectedCellId.set(null);
|
|
200
|
+
|
|
201
|
+
// Show popup if enabled
|
|
202
|
+
if (showPopup && map && e.lngLat) {
|
|
203
|
+
createSitePopup(feature.properties, e.lngLat);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function createSitePopup(siteProps: any, lngLat: mapboxgl.LngLat): void {
|
|
209
|
+
if (!map) return;
|
|
210
|
+
|
|
211
|
+
// Remove existing popup
|
|
212
|
+
if (popup) {
|
|
213
|
+
popup.remove();
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Build popup HTML
|
|
217
|
+
const fbands = siteProps.fbands ? JSON.parse(siteProps.fbands) : [];
|
|
218
|
+
const fbandsHtml = fbands.length > 0
|
|
219
|
+
? `<div><strong>Bands:</strong> ${fbands.join(', ')} MHz</div>`
|
|
220
|
+
: '';
|
|
221
|
+
|
|
222
|
+
const html = `
|
|
223
|
+
<div style="font-family: system-ui, -apple-system, sans-serif; font-size: 13px;">
|
|
224
|
+
<h6 style="margin: 0 0 8px 0; font-size: 14px; font-weight: 600;">${siteProps.name}</h6>
|
|
225
|
+
<div style="color: #666; line-height: 1.6;">
|
|
226
|
+
<div><strong>ID:</strong> ${siteProps.id}</div>
|
|
227
|
+
<div><strong>Technology:</strong> <span class="badge bg-primary">${siteProps.technology}</span></div>
|
|
228
|
+
${fbandsHtml}
|
|
229
|
+
<div><strong>Provider:</strong> ${siteProps.provider}</div>
|
|
230
|
+
<div><strong>Group:</strong> ${siteProps.featureGroup}</div>
|
|
231
|
+
<div><strong>Cells:</strong> ${siteProps.cellNames ? JSON.parse(siteProps.cellNames).length : 0}</div>
|
|
232
|
+
<div style="margin-top: 8px; padding-top: 8px; border-top: 1px solid #eee; font-size: 11px;">
|
|
233
|
+
<strong>Coordinates:</strong><br/>
|
|
234
|
+
${siteProps.latitude.toFixed(6)}, ${siteProps.longitude.toFixed(6)}
|
|
235
|
+
</div>
|
|
236
|
+
</div>
|
|
237
|
+
</div>
|
|
238
|
+
`;
|
|
239
|
+
|
|
240
|
+
// Create and add popup
|
|
241
|
+
popup = new mapboxgl.Popup({
|
|
242
|
+
closeButton: true,
|
|
243
|
+
closeOnClick: false,
|
|
244
|
+
maxWidth: '300px'
|
|
245
|
+
})
|
|
246
|
+
.setLngLat(lngLat)
|
|
247
|
+
.setHTML(html)
|
|
248
|
+
.addTo(map);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function handleMouseEnter(e: mapboxgl.MapLayerMouseEvent): void {
|
|
252
|
+
if (!map) return;
|
|
253
|
+
map.getCanvas().style.cursor = 'pointer';
|
|
254
|
+
if (!e.features || e.features.length === 0) return;
|
|
255
|
+
const feature = e.features[0];
|
|
256
|
+
const siteId = feature.properties?.id;
|
|
257
|
+
if (siteId) {
|
|
258
|
+
cellDataContext.hoveredSiteId.set(siteId);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function handleMouseLeave(): void {
|
|
263
|
+
if (!map) return;
|
|
264
|
+
map.getCanvas().style.cursor = '';
|
|
265
|
+
cellDataContext.hoveredSiteId.set(null);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function cleanup(): void {
|
|
269
|
+
if (popup) {
|
|
270
|
+
popup.remove();
|
|
271
|
+
popup = null;
|
|
272
|
+
}
|
|
273
|
+
if (map && layerManager) {
|
|
274
|
+
if (clickable) {
|
|
275
|
+
map.off('click', layerManager.getLayerId(), handleClick);
|
|
276
|
+
map.off('mouseenter', layerManager.getLayerId(), handleMouseEnter);
|
|
277
|
+
map.off('mouseleave', layerManager.getLayerId(), handleMouseLeave);
|
|
278
|
+
}
|
|
279
|
+
layerManager.cleanup();
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
</script>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
/** Namespace for layer IDs (default: 'cellular') */
|
|
3
|
+
namespace?: string;
|
|
4
|
+
/** Enable click to select sites (default: true) */
|
|
5
|
+
clickable?: boolean;
|
|
6
|
+
/** Show popup on click (default: true) */
|
|
7
|
+
showPopup?: boolean;
|
|
8
|
+
/** Site circle radius (default: 8) */
|
|
9
|
+
circleRadius?: number;
|
|
10
|
+
/** Site circle color (default: '#3b82f6') */
|
|
11
|
+
circleColor?: string;
|
|
12
|
+
/** Selected site color (default: '#ef4444') */
|
|
13
|
+
selectedColor?: string;
|
|
14
|
+
/** Hovered site color (default: '#8b5cf6') */
|
|
15
|
+
hoverColor?: string;
|
|
16
|
+
}
|
|
17
|
+
declare const SitesLayer: import("svelte").Component<Props, {}, "">;
|
|
18
|
+
type SitesLayer = ReturnType<typeof SitesLayer>;
|
|
19
|
+
export default SitesLayer;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* CellDataProvider - Provides cellular data (sites, cells) and interaction state to child components
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* <CellDataProvider {sites} {cells}>
|
|
7
|
+
* <SitesLayer />
|
|
8
|
+
* <CellsLayer />
|
|
9
|
+
* </CellDataProvider>
|
|
10
|
+
*/
|
|
11
|
+
import { setContext } from 'svelte';
|
|
12
|
+
import type { Site, Cell } from '../types';
|
|
13
|
+
import { CELL_DATA_CONTEXT_KEY } from '../types';
|
|
14
|
+
import { createCellDataStores } from '../stores/cellDataStore';
|
|
15
|
+
|
|
16
|
+
interface Props {
|
|
17
|
+
/** Array of cellular sites */
|
|
18
|
+
sites?: Site[];
|
|
19
|
+
/** Array of cellular cells/sectors */
|
|
20
|
+
cells?: Cell[];
|
|
21
|
+
/** Optional child content */
|
|
22
|
+
children?: import('svelte').Snippet;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
let { sites = [], cells = [], children }: Props = $props();
|
|
26
|
+
|
|
27
|
+
// Create and set the cell data context in context
|
|
28
|
+
const cellDataContext = createCellDataStores(sites, cells);
|
|
29
|
+
setContext(CELL_DATA_CONTEXT_KEY, cellDataContext);
|
|
30
|
+
|
|
31
|
+
// Update stores when props change
|
|
32
|
+
$effect(() => {
|
|
33
|
+
cellDataContext.sites.set(sites);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
$effect(() => {
|
|
37
|
+
cellDataContext.cells.set(cells);
|
|
38
|
+
});
|
|
39
|
+
</script>
|
|
40
|
+
|
|
41
|
+
{#if children}
|
|
42
|
+
{@render children()}
|
|
43
|
+
{/if}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Site, Cell } from '../types';
|
|
2
|
+
interface Props {
|
|
3
|
+
/** Array of cellular sites */
|
|
4
|
+
sites?: Site[];
|
|
5
|
+
/** Array of cellular cells/sectors */
|
|
6
|
+
cells?: Cell[];
|
|
7
|
+
/** Optional child content */
|
|
8
|
+
children?: import('svelte').Snippet;
|
|
9
|
+
}
|
|
10
|
+
declare const CellDataProvider: import("svelte").Component<Props, {}, "">;
|
|
11
|
+
type CellDataProvider = ReturnType<typeof CellDataProvider>;
|
|
12
|
+
export default CellDataProvider;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* MapboxProvider - Provides Mapbox GL JS map instance to child components via context
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* <MapboxProvider {mapInstance}>
|
|
7
|
+
* <SitesLayer />
|
|
8
|
+
* <CellsLayer />
|
|
9
|
+
* </MapboxProvider>
|
|
10
|
+
*/
|
|
11
|
+
import { setContext } from 'svelte';
|
|
12
|
+
import type mapboxgl from 'mapbox-gl';
|
|
13
|
+
import { MAP_CONTEXT_KEY } from '../types';
|
|
14
|
+
import { createMapStore } from '../stores/mapStore';
|
|
15
|
+
|
|
16
|
+
interface Props {
|
|
17
|
+
/** The Mapbox GL JS map instance */
|
|
18
|
+
mapInstance: mapboxgl.Map;
|
|
19
|
+
/** Optional child content */
|
|
20
|
+
children?: import('svelte').Snippet;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
let { mapInstance, children }: Props = $props();
|
|
24
|
+
|
|
25
|
+
// Create and set the map store in context
|
|
26
|
+
const mapStore = createMapStore();
|
|
27
|
+
setContext(MAP_CONTEXT_KEY, mapStore);
|
|
28
|
+
|
|
29
|
+
// Update the store whenever the map instance changes
|
|
30
|
+
$effect(() => {
|
|
31
|
+
console.log('MapboxProvider: Setting map instance', mapInstance);
|
|
32
|
+
mapStore.set(mapInstance);
|
|
33
|
+
});
|
|
34
|
+
</script>
|
|
35
|
+
|
|
36
|
+
{#if children}
|
|
37
|
+
{@render children()}
|
|
38
|
+
{/if}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
/** The Mapbox GL JS map instance */
|
|
3
|
+
mapInstance: mapboxgl.Map;
|
|
4
|
+
/** Optional child content */
|
|
5
|
+
children?: import('svelte').Snippet;
|
|
6
|
+
}
|
|
7
|
+
declare const MapboxProvider: import("svelte").Component<Props, {}, "">;
|
|
8
|
+
type MapboxProvider = ReturnType<typeof MapboxProvider>;
|
|
9
|
+
export default MapboxProvider;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider helper utilities
|
|
3
|
+
*/
|
|
4
|
+
import type { MapStore } from '../types';
|
|
5
|
+
import type { Site, Cell, CellDataContext } from '../types';
|
|
6
|
+
/**
|
|
7
|
+
* Creates a new map store instance
|
|
8
|
+
*/
|
|
9
|
+
export declare function createMap(): MapStore;
|
|
10
|
+
/**
|
|
11
|
+
* Creates a new cell data context
|
|
12
|
+
*/
|
|
13
|
+
export declare function createCellContext(sites?: Site[], cells?: Cell[]): CellDataContext;
|
|
14
|
+
/**
|
|
15
|
+
* Validates that a map instance is ready for use
|
|
16
|
+
*/
|
|
17
|
+
export declare function isMapReady(map: unknown): boolean;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider helper utilities
|
|
3
|
+
*/
|
|
4
|
+
import { createMapStore } from '../stores/mapStore';
|
|
5
|
+
import { createCellDataStores } from '../stores/cellDataStore';
|
|
6
|
+
/**
|
|
7
|
+
* Creates a new map store instance
|
|
8
|
+
*/
|
|
9
|
+
export function createMap() {
|
|
10
|
+
return createMapStore();
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Creates a new cell data context
|
|
14
|
+
*/
|
|
15
|
+
export function createCellContext(sites = [], cells = []) {
|
|
16
|
+
return createCellDataStores(sites, cells);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Validates that a map instance is ready for use
|
|
20
|
+
*/
|
|
21
|
+
export function isMapReady(map) {
|
|
22
|
+
if (!map)
|
|
23
|
+
return false;
|
|
24
|
+
// Check if it has the basic Mapbox GL Map interface
|
|
25
|
+
return typeof map.getStyle === 'function';
|
|
26
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Store factory and utilities for cellular data
|
|
3
|
+
*/
|
|
4
|
+
import type { Writable, Readable } from 'svelte/store';
|
|
5
|
+
import type { Site, Cell, CellDataContext } from '../types';
|
|
6
|
+
/**
|
|
7
|
+
* Creates a complete cell data context with all required stores
|
|
8
|
+
*/
|
|
9
|
+
export declare function createCellDataStores(initialSites?: Site[], initialCells?: Cell[]): CellDataContext;
|
|
10
|
+
/**
|
|
11
|
+
* Creates a derived store that filters cells by site ID
|
|
12
|
+
*/
|
|
13
|
+
export declare function createCellsBySiteStore(cells: Writable<Cell[]>, siteId: Writable<string | null>): Readable<Cell[]>;
|
|
14
|
+
/**
|
|
15
|
+
* Creates a derived store for the currently selected site
|
|
16
|
+
*/
|
|
17
|
+
export declare function createSelectedSiteStore(sites: Writable<Site[]>, selectedSiteId: Writable<string | null>): Readable<Site | null>;
|
|
18
|
+
/**
|
|
19
|
+
* Creates a derived store for the currently selected cell
|
|
20
|
+
*/
|
|
21
|
+
export declare function createSelectedCellStore(cells: Writable<Cell[]>, selectedCellId: Writable<string | null>): Readable<Cell | null>;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Store factory and utilities for cellular data
|
|
3
|
+
*/
|
|
4
|
+
import { writable, derived } from 'svelte/store';
|
|
5
|
+
/**
|
|
6
|
+
* Creates a complete cell data context with all required stores
|
|
7
|
+
*/
|
|
8
|
+
export function createCellDataStores(initialSites = [], initialCells = []) {
|
|
9
|
+
const sites = writable(initialSites);
|
|
10
|
+
const cells = writable(initialCells);
|
|
11
|
+
const selectedSiteId = writable(null);
|
|
12
|
+
const selectedCellId = writable(null);
|
|
13
|
+
const hoveredSiteId = writable(null);
|
|
14
|
+
const hoveredCellId = writable(null);
|
|
15
|
+
return {
|
|
16
|
+
sites,
|
|
17
|
+
cells,
|
|
18
|
+
selectedSiteId,
|
|
19
|
+
selectedCellId,
|
|
20
|
+
hoveredSiteId,
|
|
21
|
+
hoveredCellId
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Creates a derived store that filters cells by site ID
|
|
26
|
+
*/
|
|
27
|
+
export function createCellsBySiteStore(cells, siteId) {
|
|
28
|
+
return derived([cells, siteId], ([$cells, $siteId]) => {
|
|
29
|
+
if (!$siteId)
|
|
30
|
+
return [];
|
|
31
|
+
return $cells.filter((cell) => cell.siteId === $siteId);
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Creates a derived store for the currently selected site
|
|
36
|
+
*/
|
|
37
|
+
export function createSelectedSiteStore(sites, selectedSiteId) {
|
|
38
|
+
return derived([sites, selectedSiteId], ([$sites, $selectedSiteId]) => {
|
|
39
|
+
if (!$selectedSiteId)
|
|
40
|
+
return null;
|
|
41
|
+
return $sites.find((site) => site.id === $selectedSiteId) ?? null;
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Creates a derived store for the currently selected cell
|
|
46
|
+
*/
|
|
47
|
+
export function createSelectedCellStore(cells, selectedCellId) {
|
|
48
|
+
return derived([cells, selectedCellId], ([$cells, $selectedCellId]) => {
|
|
49
|
+
if (!$selectedCellId)
|
|
50
|
+
return null;
|
|
51
|
+
return $cells.find((cell) => cell.id === $selectedCellId) ?? null;
|
|
52
|
+
});
|
|
53
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interaction state utilities and helpers
|
|
3
|
+
*/
|
|
4
|
+
import type { Writable } from 'svelte/store';
|
|
5
|
+
/**
|
|
6
|
+
* Helper to clear all selections
|
|
7
|
+
*/
|
|
8
|
+
export declare function clearSelections(selectedSiteId: Writable<string | null>, selectedCellId: Writable<string | null>): void;
|
|
9
|
+
/**
|
|
10
|
+
* Helper to clear all hover states
|
|
11
|
+
*/
|
|
12
|
+
export declare function clearHovers(hoveredSiteId: Writable<string | null>, hoveredCellId: Writable<string | null>): void;
|
|
13
|
+
/**
|
|
14
|
+
* Helper to select a site and clear cell selection
|
|
15
|
+
*/
|
|
16
|
+
export declare function selectSite(siteId: string | null, selectedSiteId: Writable<string | null>, selectedCellId: Writable<string | null>): void;
|
|
17
|
+
/**
|
|
18
|
+
* Helper to select a cell (and optionally its parent site)
|
|
19
|
+
*/
|
|
20
|
+
export declare function selectCell(cellId: string | null, selectedCellId: Writable<string | null>, parentSiteId?: string | null, selectedSiteId?: Writable<string | null>): void;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interaction state utilities and helpers
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Helper to clear all selections
|
|
6
|
+
*/
|
|
7
|
+
export function clearSelections(selectedSiteId, selectedCellId) {
|
|
8
|
+
selectedSiteId.set(null);
|
|
9
|
+
selectedCellId.set(null);
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Helper to clear all hover states
|
|
13
|
+
*/
|
|
14
|
+
export function clearHovers(hoveredSiteId, hoveredCellId) {
|
|
15
|
+
hoveredSiteId.set(null);
|
|
16
|
+
hoveredCellId.set(null);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Helper to select a site and clear cell selection
|
|
20
|
+
*/
|
|
21
|
+
export function selectSite(siteId, selectedSiteId, selectedCellId) {
|
|
22
|
+
selectedSiteId.set(siteId);
|
|
23
|
+
selectedCellId.set(null);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Helper to select a cell (and optionally its parent site)
|
|
27
|
+
*/
|
|
28
|
+
export function selectCell(cellId, selectedCellId, parentSiteId, selectedSiteId) {
|
|
29
|
+
selectedCellId.set(cellId);
|
|
30
|
+
if (selectedSiteId && parentSiteId !== undefined) {
|
|
31
|
+
selectedSiteId.set(parentSiteId);
|
|
32
|
+
}
|
|
33
|
+
}
|