@smartnet360/svelte-components 0.0.74 → 0.0.76
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/map-v2/core/providers/MapboxProvider.svelte +29 -0
- package/dist/map-v2/core/providers/MapboxProvider.svelte.d.ts +2 -0
- package/dist/map-v2/features/cells/layers/CellsLayer.svelte +127 -106
- package/dist/map-v2/features/cells/stores/cellStoreContext.svelte.js +4 -16
- package/dist/map-v2/features/cells/utils/cellGeoJSON.js +2 -0
- package/dist/map-v2/features/repeaters/constants/techBandZOrder.d.ts +4 -17
- package/dist/map-v2/features/repeaters/constants/techBandZOrder.js +17 -34
- package/dist/map-v2/features/repeaters/index.d.ts +1 -1
- package/dist/map-v2/features/repeaters/index.js +1 -1
- package/dist/map-v2/features/repeaters/layers/RepeatersLayer.svelte +114 -98
- package/dist/map-v2/features/repeaters/stores/repeaterStoreContext.svelte.js +4 -17
- package/dist/map-v2/features/repeaters/utils/repeaterGeoJSON.js +24 -10
- package/dist/map-v2/index.d.ts +1 -1
- package/dist/map-v2/index.js +1 -1
- package/dist/map-v2/shared/controls/FeatureSettingsControl.svelte +55 -142
- package/dist/map-v2/shared/controls/MapControl.svelte +16 -6
- package/dist/map-v2/shared/controls/MapControl.svelte.d.ts +4 -0
- package/dist/map-v2/shared/controls/panels/CellSettingsPanel.svelte +240 -306
- package/dist/map-v2/shared/controls/panels/RepeaterSettingsPanel.svelte +180 -232
- package/dist/map-v2/shared/controls/panels/SiteSettingsPanel.svelte +119 -178
- package/package.json +7 -7
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
import { useMapbox } from '../../../core/hooks/useMapbox';
|
|
18
18
|
import { repeatersToGeoJSON } from '../utils/repeaterGeoJSON';
|
|
19
19
|
import { REPEATER_FILL_Z_INDEX, REPEATER_LINE_Z_INDEX } from '../constants/zIndex';
|
|
20
|
+
import { Z_INDEX_BY_BAND } from '../constants/techBandZOrder';
|
|
20
21
|
|
|
21
22
|
interface Props {
|
|
22
23
|
/** Repeater store context */
|
|
@@ -26,28 +27,34 @@
|
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
let { store, namespace }: Props = $props();
|
|
29
|
-
|
|
30
|
-
const FILL_LAYER_ID = `${namespace}-repeaters-fill`;
|
|
31
|
-
const LINE_LAYER_ID = `${namespace}-repeaters-line`;
|
|
30
|
+
|
|
32
31
|
const SOURCE_ID = `${namespace}-repeaters`;
|
|
33
|
-
|
|
32
|
+
|
|
34
33
|
// Get map from mapbox hook
|
|
35
34
|
const mapStore = useMapbox();
|
|
36
|
-
|
|
35
|
+
|
|
37
36
|
let map = $state<MapboxMap | null>(null);
|
|
38
37
|
let mounted = $state(false);
|
|
39
38
|
let viewportUpdateTimer: ReturnType<typeof setTimeout> | null = null;
|
|
40
39
|
let viewportVersion = $state(0); // Increment to force $effect re-run on viewport changes
|
|
41
|
-
|
|
40
|
+
|
|
41
|
+
// Get all unique z-index values and sort them
|
|
42
|
+
const sortedZIndexes = [...new Set(Object.values(Z_INDEX_BY_BAND))].sort((a, b) => a - b);
|
|
43
|
+
|
|
44
|
+
// Create layer IDs for each z-index
|
|
45
|
+
const fillLayerIds = sortedZIndexes.map((zIndex) => `${namespace}-repeaters-fill-${zIndex}`);
|
|
46
|
+
const lineLayerIds = sortedZIndexes.map((zIndex) => `${namespace}-repeaters-line-${zIndex}`);
|
|
47
|
+
const allLayerIds = [...fillLayerIds, ...lineLayerIds];
|
|
48
|
+
|
|
42
49
|
// Viewport change handler with debouncing
|
|
43
50
|
function handleMoveEnd() {
|
|
44
51
|
if (!map) return;
|
|
45
|
-
|
|
52
|
+
|
|
46
53
|
// Clear any existing timer
|
|
47
54
|
if (viewportUpdateTimer) {
|
|
48
55
|
clearTimeout(viewportUpdateTimer);
|
|
49
56
|
}
|
|
50
|
-
|
|
57
|
+
|
|
51
58
|
// Debounce: wait 200ms after last move event before re-rendering
|
|
52
59
|
viewportUpdateTimer = setTimeout(() => {
|
|
53
60
|
if (!map) return;
|
|
@@ -59,73 +66,72 @@
|
|
|
59
66
|
viewportVersion++;
|
|
60
67
|
}, 200);
|
|
61
68
|
}
|
|
62
|
-
|
|
69
|
+
|
|
63
70
|
/**
|
|
64
71
|
* Initialize or reinitialize the repeater layers
|
|
65
72
|
* Called on mount and when map style changes
|
|
66
73
|
*/
|
|
67
74
|
function initializeLayer() {
|
|
68
75
|
if (!map) return;
|
|
69
|
-
|
|
76
|
+
|
|
70
77
|
console.log('RepeatersLayer: initializeLayer called');
|
|
71
|
-
|
|
78
|
+
|
|
72
79
|
// Set initial zoom
|
|
73
80
|
store.setCurrentZoom(map.getZoom());
|
|
74
|
-
|
|
81
|
+
|
|
75
82
|
// Add moveend listener (remove first to avoid duplicates)
|
|
76
83
|
map.off('moveend', handleMoveEnd);
|
|
77
84
|
map.on('moveend', handleMoveEnd);
|
|
78
|
-
|
|
85
|
+
|
|
79
86
|
// Mark as mounted to trigger $effect
|
|
80
87
|
mounted = true;
|
|
81
|
-
|
|
88
|
+
|
|
82
89
|
// Force $effect to re-run
|
|
83
90
|
viewportVersion++;
|
|
84
91
|
}
|
|
85
|
-
|
|
92
|
+
|
|
86
93
|
onMount(() => {
|
|
87
94
|
console.log('RepeatersLayer: onMount, waiting for map...');
|
|
88
|
-
|
|
95
|
+
|
|
89
96
|
// Subscribe to map store
|
|
90
97
|
const unsubscribe = mapStore.subscribe((mapInstance) => {
|
|
91
98
|
if (mapInstance && !map) {
|
|
92
99
|
console.log('RepeatersLayer: Map available, initializing...');
|
|
93
100
|
map = mapInstance;
|
|
94
|
-
|
|
101
|
+
|
|
95
102
|
// Initial layer setup
|
|
96
103
|
initializeLayer();
|
|
97
|
-
|
|
104
|
+
|
|
98
105
|
// Re-initialize layer when map style changes
|
|
99
106
|
// Mapbox removes all custom layers/sources when setStyle() is called
|
|
100
107
|
map.on('style.load', initializeLayer);
|
|
101
108
|
}
|
|
102
109
|
});
|
|
103
|
-
|
|
110
|
+
|
|
104
111
|
return () => {
|
|
105
112
|
unsubscribe();
|
|
106
113
|
};
|
|
107
114
|
});
|
|
108
|
-
|
|
115
|
+
|
|
109
116
|
onDestroy(() => {
|
|
110
117
|
if (!map) return;
|
|
111
|
-
|
|
118
|
+
|
|
112
119
|
// Clean up timer
|
|
113
120
|
if (viewportUpdateTimer) {
|
|
114
121
|
clearTimeout(viewportUpdateTimer);
|
|
115
122
|
}
|
|
116
|
-
|
|
123
|
+
|
|
117
124
|
// Remove style.load listener
|
|
118
125
|
map.off('style.load', initializeLayer);
|
|
119
|
-
|
|
126
|
+
|
|
120
127
|
map.off('moveend', handleMoveEnd);
|
|
121
|
-
|
|
128
|
+
|
|
122
129
|
// Clean up layers and source
|
|
123
|
-
|
|
124
|
-
map.
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
}
|
|
130
|
+
allLayerIds.forEach((id) => {
|
|
131
|
+
if (map.getLayer(id)) {
|
|
132
|
+
map.removeLayer(id);
|
|
133
|
+
}
|
|
134
|
+
});
|
|
129
135
|
if (map.getSource(SOURCE_ID)) {
|
|
130
136
|
map.removeSource(SOURCE_ID);
|
|
131
137
|
}
|
|
@@ -135,7 +141,7 @@
|
|
|
135
141
|
$effect(() => {
|
|
136
142
|
// Track viewportVersion to force re-run on pan
|
|
137
143
|
viewportVersion;
|
|
138
|
-
|
|
144
|
+
|
|
139
145
|
console.log('RepeatersLayer $effect triggered:', {
|
|
140
146
|
mounted,
|
|
141
147
|
hasMap: !!map,
|
|
@@ -145,40 +151,40 @@
|
|
|
145
151
|
baseRadius: store.baseRadius,
|
|
146
152
|
viewportVersion
|
|
147
153
|
});
|
|
148
|
-
|
|
149
|
-
if (!mounted || !map
|
|
154
|
+
|
|
155
|
+
if (!mounted || !map) {
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (!store.showRepeaters) {
|
|
150
160
|
// Remove layers if showRepeaters is false
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
map.removeLayer(LINE_LAYER_ID);
|
|
155
|
-
}
|
|
156
|
-
if (map.getLayer(FILL_LAYER_ID)) {
|
|
157
|
-
map.removeLayer(FILL_LAYER_ID);
|
|
158
|
-
}
|
|
159
|
-
if (map.getSource(SOURCE_ID)) {
|
|
160
|
-
map.removeSource(SOURCE_ID);
|
|
161
|
+
allLayerIds.forEach((id) => {
|
|
162
|
+
if (map.getLayer(id)) {
|
|
163
|
+
map.removeLayer(id);
|
|
161
164
|
}
|
|
165
|
+
});
|
|
166
|
+
if (map.getSource(SOURCE_ID)) {
|
|
167
|
+
map.removeSource(SOURCE_ID);
|
|
162
168
|
}
|
|
163
169
|
return;
|
|
164
170
|
}
|
|
165
|
-
|
|
171
|
+
|
|
166
172
|
// Filter repeaters by viewport bounds (only render visible repeaters)
|
|
167
173
|
const bounds = map.getBounds();
|
|
168
174
|
if (!bounds) {
|
|
169
175
|
console.warn('RepeatersLayer: Cannot get map bounds, skipping viewport filter');
|
|
170
176
|
return;
|
|
171
177
|
}
|
|
172
|
-
|
|
173
|
-
const visibleRepeaters = store.filteredRepeaters.filter(repeater =>
|
|
178
|
+
|
|
179
|
+
const visibleRepeaters = store.filteredRepeaters.filter((repeater) =>
|
|
174
180
|
bounds.contains([repeater.longitude, repeater.latitude])
|
|
175
181
|
);
|
|
176
|
-
|
|
182
|
+
|
|
177
183
|
console.log('RepeatersLayer: Viewport filtering:', {
|
|
178
184
|
totalFiltered: store.filteredRepeaters.length,
|
|
179
185
|
visibleInViewport: visibleRepeaters.length
|
|
180
186
|
});
|
|
181
|
-
|
|
187
|
+
|
|
182
188
|
// Generate GeoJSON from visible repeaters only
|
|
183
189
|
const geoJSON = repeatersToGeoJSON(
|
|
184
190
|
visibleRepeaters,
|
|
@@ -186,12 +192,12 @@
|
|
|
186
192
|
store.baseRadius,
|
|
187
193
|
store.techBandColorMap
|
|
188
194
|
);
|
|
189
|
-
|
|
195
|
+
|
|
190
196
|
console.log('RepeatersLayer: Generated GeoJSON:', {
|
|
191
197
|
featureCount: geoJSON.features.length,
|
|
192
198
|
firstFeature: geoJSON.features[0]
|
|
193
199
|
});
|
|
194
|
-
|
|
200
|
+
|
|
195
201
|
// Update or create source
|
|
196
202
|
const source = map.getSource(SOURCE_ID);
|
|
197
203
|
if (source && source.type === 'geojson') {
|
|
@@ -204,55 +210,65 @@
|
|
|
204
210
|
data: geoJSON
|
|
205
211
|
});
|
|
206
212
|
}
|
|
207
|
-
|
|
208
|
-
//
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
layout: {
|
|
216
|
-
'fill-sort-key': ['get', 'sortKey'] // Use sortKey for z-ordering
|
|
217
|
-
},
|
|
218
|
-
paint: {
|
|
219
|
-
'fill-color': ['get', 'techBandColor'],
|
|
220
|
-
'fill-opacity': store.fillOpacity
|
|
221
|
-
},
|
|
222
|
-
metadata: {
|
|
223
|
-
zIndex: REPEATER_FILL_Z_INDEX
|
|
224
|
-
}
|
|
225
|
-
});
|
|
226
|
-
} else {
|
|
227
|
-
// Update fill opacity
|
|
228
|
-
console.log('RepeatersLayer: Updating fill opacity:', store.fillOpacity);
|
|
229
|
-
map.setPaintProperty(FILL_LAYER_ID, 'fill-opacity', store.fillOpacity);
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// Add line layer if not exists
|
|
233
|
-
if (!map.getLayer(LINE_LAYER_ID)) {
|
|
234
|
-
console.log('RepeatersLayer: Creating line layer');
|
|
235
|
-
map.addLayer({
|
|
236
|
-
id: LINE_LAYER_ID,
|
|
237
|
-
type: 'line',
|
|
238
|
-
source: SOURCE_ID,
|
|
239
|
-
layout: {
|
|
240
|
-
'line-sort-key': ['get', 'sortKey'] // Use sortKey for z-ordering
|
|
241
|
-
},
|
|
242
|
-
paint: {
|
|
243
|
-
'line-color': ['get', 'lineColor'],
|
|
244
|
-
'line-width': store.lineWidth,
|
|
245
|
-
'line-opacity': ['get', 'lineOpacity']
|
|
246
|
-
},
|
|
247
|
-
metadata: {
|
|
248
|
-
zIndex: REPEATER_LINE_Z_INDEX
|
|
249
|
-
}
|
|
250
|
-
});
|
|
251
|
-
} else {
|
|
252
|
-
// Update line width
|
|
253
|
-
console.log('RepeatersLayer: Updating line width:', store.lineWidth);
|
|
254
|
-
map.setPaintProperty(LINE_LAYER_ID, 'line-width', store.lineWidth);
|
|
213
|
+
|
|
214
|
+
// Get all unique bands for the current z-index
|
|
215
|
+
const bandsByZIndex = new Map<number, string[]>();
|
|
216
|
+
for (const [band, zIndex] of Object.entries(Z_INDEX_BY_BAND)) {
|
|
217
|
+
if (!bandsByZIndex.has(zIndex)) {
|
|
218
|
+
bandsByZIndex.set(zIndex, []);
|
|
219
|
+
}
|
|
220
|
+
bandsByZIndex.get(zIndex)?.push(band);
|
|
255
221
|
}
|
|
222
|
+
|
|
223
|
+
// Add/update layers for each z-index level
|
|
224
|
+
sortedZIndexes.forEach((zIndex) => {
|
|
225
|
+
const fillLayerId = `${namespace}-repeaters-fill-${zIndex}`;
|
|
226
|
+
const lineLayerId = `${namespace}-repeaters-line-${zIndex}`;
|
|
227
|
+
const bands = bandsByZIndex.get(zIndex) || [];
|
|
228
|
+
|
|
229
|
+
// Add fill layer if not exists
|
|
230
|
+
if (!map.getLayer(fillLayerId)) {
|
|
231
|
+
console.log(`RepeatersLayer: Creating fill layer for z-index ${zIndex}`);
|
|
232
|
+
map.addLayer({
|
|
233
|
+
id: fillLayerId,
|
|
234
|
+
type: 'fill',
|
|
235
|
+
source: SOURCE_ID,
|
|
236
|
+
filter: ['in', ['get', 'techBandKey'], ['literal', bands]],
|
|
237
|
+
paint: {
|
|
238
|
+
'fill-color': ['get', 'techBandColor'],
|
|
239
|
+
'fill-opacity': store.fillOpacity
|
|
240
|
+
},
|
|
241
|
+
metadata: {
|
|
242
|
+
zIndex: REPEATER_FILL_Z_INDEX + zIndex
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
} else {
|
|
246
|
+
// Update fill opacity
|
|
247
|
+
map.setPaintProperty(fillLayerId, 'fill-opacity', store.fillOpacity);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Add line layer if not exists
|
|
251
|
+
if (!map.getLayer(lineLayerId)) {
|
|
252
|
+
console.log(`RepeatersLayer: Creating line layer for z-index ${zIndex}`);
|
|
253
|
+
map.addLayer({
|
|
254
|
+
id: lineLayerId,
|
|
255
|
+
type: 'line',
|
|
256
|
+
source: SOURCE_ID,
|
|
257
|
+
filter: ['in', ['get', 'techBandKey'], ['literal', bands]],
|
|
258
|
+
paint: {
|
|
259
|
+
'line-color': ['get', 'lineColor'],
|
|
260
|
+
'line-width': store.lineWidth,
|
|
261
|
+
'line-opacity': ['get', 'lineOpacity']
|
|
262
|
+
},
|
|
263
|
+
metadata: {
|
|
264
|
+
zIndex: REPEATER_LINE_Z_INDEX + zIndex
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
} else {
|
|
268
|
+
// Update line width
|
|
269
|
+
map.setPaintProperty(lineLayerId, 'line-width', store.lineWidth);
|
|
270
|
+
}
|
|
271
|
+
});
|
|
256
272
|
});
|
|
257
273
|
</script>
|
|
258
274
|
|
|
@@ -90,23 +90,10 @@ export function createRepeaterStoreContext(repeaters) {
|
|
|
90
90
|
minLabelZoom: persistedSettings.minLabelZoom ?? 10 // Lower default to show labels at more zoom levels
|
|
91
91
|
});
|
|
92
92
|
// Derived: Filter repeaters by visible tech:fband combinations
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
let filteredRepeaters = $derived.by(() => {
|
|
98
|
-
// Only recompute if repeaters reference or visible tech bands changed
|
|
99
|
-
if (state.repeaters === lastRepeatersRef && state.visibleTechBands === lastVisibleTechBands) {
|
|
100
|
-
return cachedFilteredRepeaters;
|
|
101
|
-
}
|
|
102
|
-
lastRepeatersRef = state.repeaters;
|
|
103
|
-
lastVisibleTechBands = state.visibleTechBands;
|
|
104
|
-
cachedFilteredRepeaters = state.repeaters.filter(r => {
|
|
105
|
-
const key = `${r.tech}:${r.fband}`;
|
|
106
|
-
return state.visibleTechBands.has(key);
|
|
107
|
-
});
|
|
108
|
-
return cachedFilteredRepeaters;
|
|
109
|
-
});
|
|
93
|
+
let filteredRepeaters = $derived(state.repeaters.filter(r => {
|
|
94
|
+
const key = `${r.tech}:${r.fband}`;
|
|
95
|
+
return state.visibleTechBands.has(key);
|
|
96
|
+
}));
|
|
110
97
|
// Auto-save settings when they change
|
|
111
98
|
$effect(() => {
|
|
112
99
|
// Convert Set to Array and Map to Object for serialization
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { createArcPolygon } from '../../cells/utils/arcGeometry';
|
|
10
10
|
import { getRepeaterRadiusMultiplier } from '../constants/radiusMultipliers';
|
|
11
|
-
import {
|
|
11
|
+
import { Z_INDEX_BY_BAND } from '../constants/techBandZOrder';
|
|
12
12
|
/** Fixed beamwidth for all repeaters */
|
|
13
13
|
const REPEATER_BEAMWIDTH = 30;
|
|
14
14
|
/** Reference zoom level where base radius is used as-is */
|
|
@@ -26,6 +26,18 @@ const REFERENCE_ZOOM = 12;
|
|
|
26
26
|
function getZoomFactor(currentZoom) {
|
|
27
27
|
return Math.pow(2, (REFERENCE_ZOOM - currentZoom) / 2);
|
|
28
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* Parse repeater tech and fband into a TechnologyBandKey
|
|
31
|
+
* Repeaters use format like tech="4G", fband="LTE1800" or "1800"
|
|
32
|
+
* We need to convert to "4G_1800" format
|
|
33
|
+
*/
|
|
34
|
+
function parseTechBand(tech, fband) {
|
|
35
|
+
// Extract numeric band from fband (handles "LTE1800", "1800", "GSM900", "900", etc.)
|
|
36
|
+
const bandMatch = fband.match(/(\d+)/);
|
|
37
|
+
const band = bandMatch ? bandMatch[1] : fband;
|
|
38
|
+
// Build TechnologyBandKey
|
|
39
|
+
return `${tech}_${band}`;
|
|
40
|
+
}
|
|
29
41
|
/**
|
|
30
42
|
* Convert repeaters to GeoJSON FeatureCollection with arc geometries
|
|
31
43
|
*
|
|
@@ -37,16 +49,18 @@ function getZoomFactor(currentZoom) {
|
|
|
37
49
|
*/
|
|
38
50
|
export function repeatersToGeoJSON(repeaters, currentZoom, baseRadius = 500, techBandColorMap) {
|
|
39
51
|
const features = repeaters.map((repeater) => {
|
|
40
|
-
// Get color for this tech:fband combination
|
|
41
|
-
const
|
|
42
|
-
const color = techBandColorMap.get(
|
|
52
|
+
// Get color for this tech:fband combination (old format for color map)
|
|
53
|
+
const colorKey = `${repeater.tech}:${repeater.fband}`;
|
|
54
|
+
const color = techBandColorMap.get(colorKey) || '#888888'; // Fallback gray
|
|
55
|
+
// Parse to TechnologyBandKey for z-index lookup
|
|
56
|
+
const techBandKey = parseTechBand(repeater.tech, repeater.fband);
|
|
43
57
|
// Calculate zoom-reactive radius with inverse scaling (like cells)
|
|
44
58
|
const zoomFactor = getZoomFactor(currentZoom);
|
|
45
59
|
// Apply tech-band specific radius multiplier
|
|
46
60
|
const techBandMultiplier = getRepeaterRadiusMultiplier(repeater.tech, repeater.fband);
|
|
47
61
|
const radius = baseRadius * zoomFactor * techBandMultiplier;
|
|
48
|
-
// Get z-order for layering
|
|
49
|
-
const
|
|
62
|
+
// Get z-order for layering using shared constant
|
|
63
|
+
const zIndex = Z_INDEX_BY_BAND[techBandKey] || 0;
|
|
50
64
|
// Create arc polygon geometry with fixed 30° beamwidth
|
|
51
65
|
const center = [repeater.longitude, repeater.latitude];
|
|
52
66
|
const arc = createArcPolygon(center, repeater.azimuth, REPEATER_BEAMWIDTH, radius);
|
|
@@ -73,13 +87,13 @@ export function repeatersToGeoJSON(repeaters, currentZoom, baseRadius = 500, tec
|
|
|
73
87
|
featureGroup: repeater.featureGroup || '',
|
|
74
88
|
status: repeater.status || 'active',
|
|
75
89
|
// Styling properties for Mapbox
|
|
76
|
-
techBandKey:
|
|
90
|
+
techBandKey: techBandKey, // TechnologyBandKey format for consistency
|
|
77
91
|
techBandColor: color,
|
|
78
92
|
lineColor: color,
|
|
79
93
|
lineOpacity: 0.8,
|
|
80
|
-
// Z-
|
|
81
|
-
|
|
82
|
-
sortKey:
|
|
94
|
+
// Z-index for layering (higher frequency on top)
|
|
95
|
+
zIndex: zIndex,
|
|
96
|
+
sortKey: zIndex // Used by Mapbox for layer ordering
|
|
83
97
|
}
|
|
84
98
|
};
|
|
85
99
|
});
|
package/dist/map-v2/index.d.ts
CHANGED
|
@@ -8,5 +8,5 @@ export { type MapStore, MAP_CONTEXT_KEY, MapboxProvider, ViewportSync, MapStoreB
|
|
|
8
8
|
export { MapControl, FeatureSettingsControl, addSourceIfMissing, removeSourceIfExists, addLayerIfMissing, removeLayerIfExists, updateGeoJSONSource, removeLayerAndSource, isStyleLoaded, waitForStyleLoad, setFeatureState, removeFeatureState, generateLayerId, generateSourceId } from './shared';
|
|
9
9
|
export { type Site, type SiteStoreValue, type SiteStoreContext, createSiteStore, createSiteStoreContext, SitesLayer, SiteFilterControl, SiteSelectionControl, SiteSizeSlider, sitesToGeoJSON, siteToFeature, buildSiteTree, getFilteredSites } from './features/sites';
|
|
10
10
|
export { type Cell, type CellStatus, type CellStatusStyle, type CellGroupingField, type CellGroupingLabels, type CellTreeConfig, type TechnologyBandKey, type CellStoreValue, type CellStoreContext, createCellStoreContext, CellsLayer, CellLabelsLayer, CellFilterControl, CellStyleControl, cellsToGeoJSON, buildCellTree, getFilteredCells, calculateRadius, getZoomFactor, createArcPolygon, DEFAULT_CELL_TREE_CONFIG, TECHNOLOGY_BAND_COLORS, DEFAULT_STATUS_STYLES, RADIUS_MULTIPLIER } from './features/cells';
|
|
11
|
-
export { type Repeater, type RepeaterTreeNode, type RepeaterStoreValue, type RepeaterStoreContext, createRepeaterStoreContext, RepeatersLayer, RepeaterLabelsLayer, RepeaterFilterControl, repeatersToGeoJSON, buildRepeaterTree, getFilteredRepeaters, getRepeaterRadiusMultiplier,
|
|
11
|
+
export { type Repeater, type RepeaterTreeNode, type RepeaterStoreValue, type RepeaterStoreContext, createRepeaterStoreContext, RepeatersLayer, RepeaterLabelsLayer, RepeaterFilterControl, repeatersToGeoJSON, buildRepeaterTree, getFilteredRepeaters, getRepeaterRadiusMultiplier, REPEATER_FILL_Z_INDEX, REPEATER_LINE_Z_INDEX, REPEATER_LABEL_Z_INDEX, REPEATER_RADIUS_MULTIPLIER } from './features/repeaters';
|
|
12
12
|
export { DemoMap, demoSites, demoCells, demoRepeaters } from './demo';
|
package/dist/map-v2/index.js
CHANGED
|
@@ -23,7 +23,7 @@ export { createCellStoreContext, CellsLayer, CellLabelsLayer, CellFilterControl,
|
|
|
23
23
|
// ============================================================================
|
|
24
24
|
// REPEATER FEATURE
|
|
25
25
|
// ============================================================================
|
|
26
|
-
export { createRepeaterStoreContext, RepeatersLayer, RepeaterLabelsLayer, RepeaterFilterControl, repeatersToGeoJSON, buildRepeaterTree, getFilteredRepeaters, getRepeaterRadiusMultiplier,
|
|
26
|
+
export { createRepeaterStoreContext, RepeatersLayer, RepeaterLabelsLayer, RepeaterFilterControl, repeatersToGeoJSON, buildRepeaterTree, getFilteredRepeaters, getRepeaterRadiusMultiplier, REPEATER_FILL_Z_INDEX, REPEATER_LINE_Z_INDEX, REPEATER_LABEL_Z_INDEX, REPEATER_RADIUS_MULTIPLIER } from './features/repeaters';
|
|
27
27
|
// ============================================================================
|
|
28
28
|
// DEMO
|
|
29
29
|
// ============================================================================
|
|
@@ -53,160 +53,73 @@
|
|
|
53
53
|
iconOnlyWhenCollapsed = true,
|
|
54
54
|
initiallyCollapsed = true
|
|
55
55
|
}: Props = $props();
|
|
56
|
-
|
|
57
|
-
// Generate unique IDs for accordion items
|
|
58
|
-
const accordionId = `settings-accordion-${Math.random().toString(36).substring(7)}`;
|
|
59
56
|
</script>
|
|
60
57
|
|
|
61
|
-
<MapControl {position} {title} {icon} {iconOnlyWhenCollapsed} collapsible={true} {initiallyCollapsed}>
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
<
|
|
65
|
-
<
|
|
66
|
-
<button
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
aria-labelledby="heading-sites"
|
|
81
|
-
>
|
|
82
|
-
<div class="accordion-body">
|
|
83
|
-
<SiteSettingsPanel store={siteStore} />
|
|
84
|
-
</div>
|
|
58
|
+
<MapControl {position} {title} {icon} {iconOnlyWhenCollapsed} collapsible={true} {initiallyCollapsed} controlWidth="380px" edgeOffset="12px">
|
|
59
|
+
<!-- use CSS variables for width & offsets so MapControl can pick these up -->
|
|
60
|
+
<div class="d-flex flex-column p-0">
|
|
61
|
+
<ul class="nav nav-tabs" id="settings-tabs" role="tablist">
|
|
62
|
+
<li class="nav-item" role="presentation">
|
|
63
|
+
<button class="nav-link active" id="site-tab" data-bs-toggle="tab" data-bs-target="#site" type="button" role="tab" aria-controls="site" aria-selected="true">Site</button>
|
|
64
|
+
</li>
|
|
65
|
+
<li class="nav-item" role="presentation">
|
|
66
|
+
<button class="nav-link" id="cell-tab" data-bs-toggle="tab" data-bs-target="#cell" type="button" role="tab" aria-controls="cell" aria-selected="false">Cell</button>
|
|
67
|
+
</li>
|
|
68
|
+
{#if repeaterStore}
|
|
69
|
+
<li class="nav-item" role="presentation">
|
|
70
|
+
<button class="nav-link" id="repeater-tab" data-bs-toggle="tab" data-bs-target="#repeater" type="button" role="tab" aria-controls="repeater" aria-selected="false">Repeater</button>
|
|
71
|
+
</li>
|
|
72
|
+
{/if}
|
|
73
|
+
</ul>
|
|
74
|
+
<div class="tab-content rounded-2 shadow-sm bg-white border p-2" id="settings-tab-content" style="width: 360px;">
|
|
75
|
+
<div class="tab-pane show active" id="site" role="tabpanel" aria-labelledby="site-tab">
|
|
76
|
+
<SiteSettingsPanel store={siteStore} />
|
|
85
77
|
</div>
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
type="button"
|
|
94
|
-
data-bs-toggle="collapse"
|
|
95
|
-
data-bs-target="#collapse-cells"
|
|
96
|
-
aria-expanded="false"
|
|
97
|
-
aria-controls="collapse-cells"
|
|
98
|
-
>
|
|
99
|
-
Cell Settings
|
|
100
|
-
</button>
|
|
101
|
-
</h2>
|
|
102
|
-
<div
|
|
103
|
-
id="collapse-cells"
|
|
104
|
-
class="accordion-collapse collapse"
|
|
105
|
-
aria-labelledby="heading-cells"
|
|
106
|
-
>
|
|
107
|
-
<div class="accordion-body">
|
|
108
|
-
<CellSettingsPanel
|
|
109
|
-
store={cellStore}
|
|
110
|
-
{labelFieldOptions4G5G}
|
|
111
|
-
{labelFieldOptions2G}
|
|
112
|
-
{fieldLabels}
|
|
113
|
-
/>
|
|
114
|
-
</div>
|
|
78
|
+
<div class="tab-pane" id="cell" role="tabpanel" aria-labelledby="cell-tab">
|
|
79
|
+
<CellSettingsPanel
|
|
80
|
+
store={cellStore}
|
|
81
|
+
{labelFieldOptions4G5G}
|
|
82
|
+
{labelFieldOptions2G}
|
|
83
|
+
{fieldLabels}
|
|
84
|
+
/>
|
|
115
85
|
</div>
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
{#if repeaterStore}
|
|
120
|
-
<div class="accordion-item">
|
|
121
|
-
<h2 class="accordion-header" id="heading-repeaters">
|
|
122
|
-
<button
|
|
123
|
-
class="accordion-button collapsed"
|
|
124
|
-
type="button"
|
|
125
|
-
data-bs-toggle="collapse"
|
|
126
|
-
data-bs-target="#collapse-repeaters"
|
|
127
|
-
aria-expanded="false"
|
|
128
|
-
aria-controls="collapse-repeaters"
|
|
129
|
-
>
|
|
130
|
-
Repeater Settings
|
|
131
|
-
</button>
|
|
132
|
-
</h2>
|
|
133
|
-
<div
|
|
134
|
-
id="collapse-repeaters"
|
|
135
|
-
class="accordion-collapse collapse"
|
|
136
|
-
aria-labelledby="heading-repeaters"
|
|
137
|
-
>
|
|
138
|
-
<div class="accordion-body">
|
|
139
|
-
<RepeaterSettingsPanel store={repeaterStore} />
|
|
140
|
-
</div>
|
|
86
|
+
{#if repeaterStore}
|
|
87
|
+
<div class="tab-pane" id="repeater" role="tabpanel" aria-labelledby="repeater-tab">
|
|
88
|
+
<RepeaterSettingsPanel store={repeaterStore} />
|
|
141
89
|
</div>
|
|
142
|
-
|
|
143
|
-
|
|
90
|
+
{/if}
|
|
91
|
+
</div>
|
|
144
92
|
</div>
|
|
145
93
|
</MapControl>
|
|
146
94
|
|
|
147
95
|
<style>
|
|
148
|
-
.
|
|
149
|
-
|
|
150
|
-
/* Fixed width to prevent resizing when sections expand/collapse */
|
|
151
|
-
width: 320px;
|
|
152
|
-
min-width: 320px;
|
|
153
|
-
max-width: 320px;
|
|
96
|
+
:global(.map-control-content) {
|
|
97
|
+
padding: 0.25rem;
|
|
154
98
|
}
|
|
155
|
-
|
|
156
|
-
.
|
|
157
|
-
|
|
158
|
-
border: none;
|
|
159
|
-
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
.accordion-item:last-child {
|
|
163
|
-
border-bottom: none;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
.accordion-button {
|
|
167
|
-
font-size: 0.875rem;
|
|
168
|
-
font-weight: 600;
|
|
169
|
-
padding: 0.75rem 1rem;
|
|
170
|
-
background-color: transparent;
|
|
171
|
-
color: var(--bs-body-color);
|
|
172
|
-
/* Ensure chevron is visible and properly positioned */
|
|
173
|
-
position: relative;
|
|
174
|
-
display: flex;
|
|
175
|
-
align-items: center;
|
|
176
|
-
width: 100%;
|
|
177
|
-
text-align: left;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
/* Preserve Bootstrap's chevron */
|
|
181
|
-
.accordion-button::after {
|
|
182
|
-
flex-shrink: 0;
|
|
183
|
-
width: 1.25rem;
|
|
184
|
-
height: 1.25rem;
|
|
185
|
-
margin-left: auto;
|
|
186
|
-
content: "";
|
|
187
|
-
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23212529'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
|
|
188
|
-
background-repeat: no-repeat;
|
|
189
|
-
background-size: 1.25rem;
|
|
190
|
-
transition: transform 0.2s ease-in-out;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
/* Rotate chevron when expanded */
|
|
194
|
-
.accordion-button:not(.collapsed)::after {
|
|
195
|
-
transform: rotate(-180deg);
|
|
99
|
+
|
|
100
|
+
.nav-tabs {
|
|
101
|
+
border-bottom: 1px solid #dee2e6;
|
|
196
102
|
}
|
|
197
|
-
|
|
198
|
-
.
|
|
199
|
-
|
|
200
|
-
|
|
103
|
+
|
|
104
|
+
.nav-link {
|
|
105
|
+
border: 1px solid transparent;
|
|
106
|
+
border-top-left-radius: 0.375rem;
|
|
107
|
+
border-top-right-radius: 0.375rem;
|
|
108
|
+
font-size: 0.95rem;
|
|
109
|
+
font-weight: 500;
|
|
110
|
+
padding: 0.5rem 1rem; /* increased horizontal padding to fit text better */
|
|
111
|
+
color: #6c757d;
|
|
112
|
+
white-space: nowrap; /* prevent text wrapping */
|
|
113
|
+
min-width: fit-content; /* ensure tab is at least as wide as content */
|
|
201
114
|
}
|
|
202
|
-
|
|
203
|
-
.
|
|
204
|
-
|
|
205
|
-
|
|
115
|
+
|
|
116
|
+
.nav-link.active {
|
|
117
|
+
color: #495057;
|
|
118
|
+
background-color: #fff;
|
|
119
|
+
border-color: #dee2e6 #dee2e6 #fff;
|
|
206
120
|
}
|
|
207
|
-
|
|
208
|
-
.
|
|
209
|
-
padding:
|
|
210
|
-
padding-top: 0.5rem;
|
|
121
|
+
|
|
122
|
+
.tab-content {
|
|
123
|
+
padding: 0;
|
|
211
124
|
}
|
|
212
125
|
</style>
|