@smartnet360/svelte-components 0.0.71 → 0.0.73
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/demo/DemoMap.svelte +29 -1
- package/dist/map-v2/demo/demo-repeaters.d.ts +13 -0
- package/dist/map-v2/demo/demo-repeaters.js +74 -0
- package/dist/map-v2/demo/index.d.ts +1 -0
- package/dist/map-v2/demo/index.js +1 -0
- package/dist/map-v2/features/repeaters/constants/radiusMultipliers.d.ts +26 -0
- package/dist/map-v2/features/repeaters/constants/radiusMultipliers.js +40 -0
- package/dist/map-v2/features/repeaters/constants/techBandZOrder.d.ts +25 -0
- package/dist/map-v2/features/repeaters/constants/techBandZOrder.js +43 -0
- package/dist/map-v2/features/repeaters/constants/zIndex.d.ts +11 -0
- package/dist/map-v2/features/repeaters/constants/zIndex.js +11 -0
- package/dist/map-v2/features/repeaters/controls/RepeaterFilterControl.svelte +172 -0
- package/dist/map-v2/features/repeaters/controls/RepeaterFilterControl.svelte.d.ts +18 -0
- package/dist/map-v2/features/repeaters/index.d.ts +16 -0
- package/dist/map-v2/features/repeaters/index.js +19 -0
- package/dist/map-v2/features/repeaters/layers/RepeaterLabelsLayer.svelte +301 -0
- package/dist/map-v2/features/repeaters/layers/RepeaterLabelsLayer.svelte.d.ts +10 -0
- package/dist/map-v2/features/repeaters/layers/RepeatersLayer.svelte +259 -0
- package/dist/map-v2/features/repeaters/layers/RepeatersLayer.svelte.d.ts +10 -0
- package/dist/map-v2/features/repeaters/stores/repeaterStoreContext.svelte.d.ts +69 -0
- package/dist/map-v2/features/repeaters/stores/repeaterStoreContext.svelte.js +222 -0
- package/dist/map-v2/features/repeaters/types.d.ts +59 -0
- package/dist/map-v2/features/repeaters/types.js +4 -0
- package/dist/map-v2/features/repeaters/utils/repeaterGeoJSON.d.ts +20 -0
- package/dist/map-v2/features/repeaters/utils/repeaterGeoJSON.js +90 -0
- package/dist/map-v2/features/repeaters/utils/repeaterTree.d.ts +23 -0
- package/dist/map-v2/features/repeaters/utils/repeaterTree.js +111 -0
- package/dist/map-v2/index.d.ts +2 -1
- package/dist/map-v2/index.js +5 -1
- package/dist/map-v2/shared/controls/FeatureSettingsControl.svelte +27 -21
- package/dist/map-v2/shared/controls/FeatureSettingsControl.svelte.d.ts +3 -0
- package/dist/map-v2/shared/controls/panels/RepeaterSettingsPanel.svelte +280 -5
- package/dist/map-v2/shared/controls/panels/RepeaterSettingsPanel.svelte.d.ts +17 -16
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
/**
|
|
3
|
-
* DemoMap - Complete demo of map-v2 site and
|
|
3
|
+
* DemoMap - Complete demo of map-v2 site, cell, and repeater features
|
|
4
4
|
*
|
|
5
5
|
* Demonstrates:
|
|
6
6
|
* - Simplified MapboxProvider with automatic initialization
|
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
* - CellsLayer rendering cells with arc geometries
|
|
12
12
|
* - CellFilterControl with dynamic hierarchical grouping
|
|
13
13
|
* - CellStyleControl for cell visual settings
|
|
14
|
+
* - RepeatersLayer with tech:fband tree filtering
|
|
15
|
+
* - RepeaterLabelsLayer for repeater identification
|
|
14
16
|
*/
|
|
15
17
|
import MapboxProvider from '../core/providers/MapboxProvider.svelte';
|
|
16
18
|
import MapStyleControl from '../core/controls/MapStyleControl.svelte';
|
|
@@ -21,13 +23,18 @@
|
|
|
21
23
|
import CellsLayer from '../features/cells/layers/CellsLayer.svelte';
|
|
22
24
|
import CellLabelsLayer from '../features/cells/layers/CellLabelsLayer.svelte';
|
|
23
25
|
import CellFilterControl from '../features/cells/controls/CellFilterControl.svelte';
|
|
26
|
+
import RepeatersLayer from '../features/repeaters/layers/RepeatersLayer.svelte';
|
|
27
|
+
import RepeaterLabelsLayer from '../features/repeaters/layers/RepeaterLabelsLayer.svelte';
|
|
28
|
+
import RepeaterFilterControl from '../features/repeaters/controls/RepeaterFilterControl.svelte';
|
|
24
29
|
import FeatureSettingsControl from '../shared/controls/FeatureSettingsControl.svelte';
|
|
25
30
|
import { createSiteStoreContext } from '../features/sites/stores/siteStoreContext.svelte';
|
|
26
31
|
import { createCellStoreContext } from '../features/cells/stores/cellStoreContext.svelte';
|
|
32
|
+
import { createRepeaterStoreContext } from '../features/repeaters/stores/repeaterStoreContext.svelte';
|
|
27
33
|
import { createViewportStore } from '../core/stores/viewportStore.svelte';
|
|
28
34
|
import type { CellGroupingField, CellGroupingLabels } from '../features/cells/types';
|
|
29
35
|
import { demoSites } from './demo-data';
|
|
30
36
|
import { demoCells } from './demo-cells';
|
|
37
|
+
import { demoRepeaters } from './demo-repeaters';
|
|
31
38
|
import { writable } from 'svelte/store';
|
|
32
39
|
|
|
33
40
|
interface Props {
|
|
@@ -54,6 +61,9 @@
|
|
|
54
61
|
// Create cell store with demo data
|
|
55
62
|
const cellStore = createCellStoreContext(demoCells);
|
|
56
63
|
|
|
64
|
+
// Create repeater store with demo data
|
|
65
|
+
const repeaterStore = createRepeaterStoreContext(demoRepeaters);
|
|
66
|
+
|
|
57
67
|
// Toggle whether control headers show icons (when collapsed) or always show text
|
|
58
68
|
const useIconHeaders = true;
|
|
59
69
|
|
|
@@ -61,6 +71,7 @@
|
|
|
61
71
|
const controlIcons = {
|
|
62
72
|
mapStyle: 'layers',
|
|
63
73
|
cellFilter: 'radioactive',
|
|
74
|
+
repeaterFilter: 'broadcast',
|
|
64
75
|
siteFilter: 'broadcast-pin',
|
|
65
76
|
siteSelection: 'check2-square',
|
|
66
77
|
cellStyle: 'palette',
|
|
@@ -153,6 +164,12 @@
|
|
|
153
164
|
<!-- Cell labels layer - renders tech-aware labels positioned along azimuth -->
|
|
154
165
|
<CellLabelsLayer store={cellStore} namespace="demo-cells" />
|
|
155
166
|
|
|
167
|
+
<!-- Repeater layer - renders arc sectors with fixed 30° beamwidth -->
|
|
168
|
+
<RepeatersLayer store={repeaterStore} namespace="demo-repeaters" />
|
|
169
|
+
|
|
170
|
+
<!-- Repeater labels layer - renders labels positioned along azimuth -->
|
|
171
|
+
<RepeaterLabelsLayer store={repeaterStore} namespace="demo-repeaters" />
|
|
172
|
+
|
|
156
173
|
<!-- Map style control - switch between map styles -->
|
|
157
174
|
<MapStyleControl
|
|
158
175
|
position="top-right"
|
|
@@ -175,6 +192,16 @@
|
|
|
175
192
|
iconOnlyWhenCollapsed={useIconHeaders}
|
|
176
193
|
/>
|
|
177
194
|
|
|
195
|
+
<!-- Repeater filter control - Tech:FBand tree filtering -->
|
|
196
|
+
<RepeaterFilterControl
|
|
197
|
+
store={repeaterStore}
|
|
198
|
+
position="top-left"
|
|
199
|
+
title="Repeater Filter"
|
|
200
|
+
initiallyCollapsed={true}
|
|
201
|
+
icon={controlIcons.repeaterFilter}
|
|
202
|
+
iconOnlyWhenCollapsed={useIconHeaders}
|
|
203
|
+
/>
|
|
204
|
+
|
|
178
205
|
<!-- Site filter control - updates store.filteredSites -->
|
|
179
206
|
<SiteFilterControl
|
|
180
207
|
store={siteStore}
|
|
@@ -199,6 +226,7 @@
|
|
|
199
226
|
<FeatureSettingsControl
|
|
200
227
|
siteStore={siteStore}
|
|
201
228
|
cellStore={cellStore}
|
|
229
|
+
repeaterStore={repeaterStore}
|
|
202
230
|
position="top-right"
|
|
203
231
|
title="Feature Settings"
|
|
204
232
|
initiallyCollapsed={true}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Demo Repeater Data
|
|
3
|
+
*
|
|
4
|
+
* 15 repeaters strategically placed near demo sites
|
|
5
|
+
* Mix of 2G, 4G, and 5G technologies across various frequency bands
|
|
6
|
+
* Fixed 30° beamwidth
|
|
7
|
+
*/
|
|
8
|
+
import type { Repeater } from '../features/repeaters/types';
|
|
9
|
+
/**
|
|
10
|
+
* Generate demo repeater data
|
|
11
|
+
* Creates 15 repeaters with various tech:fband combinations
|
|
12
|
+
*/
|
|
13
|
+
export declare const demoRepeaters: Repeater[];
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Demo Repeater Data
|
|
3
|
+
*
|
|
4
|
+
* 15 repeaters strategically placed near demo sites
|
|
5
|
+
* Mix of 2G, 4G, and 5G technologies across various frequency bands
|
|
6
|
+
* Fixed 30° beamwidth
|
|
7
|
+
*/
|
|
8
|
+
// Base location: San Francisco
|
|
9
|
+
const BASE_LAT = 37.7749;
|
|
10
|
+
const BASE_LNG = -122.4194;
|
|
11
|
+
// Repeater positions (distributed across demo area)
|
|
12
|
+
const REPEATER_POSITIONS = [
|
|
13
|
+
{ lat: BASE_LAT + 0.015, lng: BASE_LNG - 0.020 },
|
|
14
|
+
{ lat: BASE_LAT + 0.025, lng: BASE_LNG + 0.010 },
|
|
15
|
+
{ lat: BASE_LAT - 0.010, lng: BASE_LNG + 0.025 },
|
|
16
|
+
{ lat: BASE_LAT - 0.020, lng: BASE_LNG - 0.015 },
|
|
17
|
+
{ lat: BASE_LAT + 0.005, lng: BASE_LNG + 0.030 },
|
|
18
|
+
{ lat: BASE_LAT + 0.030, lng: BASE_LNG - 0.005 },
|
|
19
|
+
{ lat: BASE_LAT - 0.025, lng: BASE_LNG + 0.015 },
|
|
20
|
+
{ lat: BASE_LAT + 0.010, lng: BASE_LNG - 0.030 },
|
|
21
|
+
{ lat: BASE_LAT - 0.015, lng: BASE_LNG - 0.025 },
|
|
22
|
+
{ lat: BASE_LAT + 0.020, lng: BASE_LNG + 0.020 },
|
|
23
|
+
{ lat: BASE_LAT - 0.005, lng: BASE_LNG - 0.010 },
|
|
24
|
+
{ lat: BASE_LAT + 0.035, lng: BASE_LNG + 0.005 },
|
|
25
|
+
{ lat: BASE_LAT - 0.030, lng: BASE_LNG + 0.020 },
|
|
26
|
+
{ lat: BASE_LAT + 0.012, lng: BASE_LNG - 0.018 },
|
|
27
|
+
{ lat: BASE_LAT - 0.018, lng: BASE_LNG + 0.012 }
|
|
28
|
+
];
|
|
29
|
+
// Repeater tech-band combinations
|
|
30
|
+
const REPEATER_CONFIGS = [
|
|
31
|
+
// 2G repeaters
|
|
32
|
+
{ tech: '2G', fband: 'GSM900', azimuth: 45 },
|
|
33
|
+
{ tech: '2G', fband: 'GSM1800', azimuth: 180 },
|
|
34
|
+
{ tech: '2G', fband: 'GSM900', azimuth: 270 },
|
|
35
|
+
// 4G repeaters
|
|
36
|
+
{ tech: '4G', fband: 'LTE700', azimuth: 90 },
|
|
37
|
+
{ tech: '4G', fband: 'LTE800', azimuth: 225 },
|
|
38
|
+
{ tech: '4G', fband: 'LTE1800', azimuth: 135 },
|
|
39
|
+
{ tech: '4G', fband: 'LTE2100', azimuth: 315 },
|
|
40
|
+
{ tech: '4G', fband: 'LTE2600', azimuth: 60 },
|
|
41
|
+
// 5G repeaters
|
|
42
|
+
{ tech: '5G', fband: '5G-700', azimuth: 150 },
|
|
43
|
+
{ tech: '5G', fband: '5G-2100', azimuth: 210 },
|
|
44
|
+
{ tech: '5G', fband: '5G-3500', azimuth: 330 },
|
|
45
|
+
{ tech: '5G', fband: '5G-3500', azimuth: 0 },
|
|
46
|
+
{ tech: '5G', fband: '5G-2100', azimuth: 120 },
|
|
47
|
+
{ tech: '5G', fband: '5G-700', azimuth: 240 },
|
|
48
|
+
{ tech: '5G', fband: '5G-3500', azimuth: 180 }
|
|
49
|
+
];
|
|
50
|
+
/**
|
|
51
|
+
* Generate demo repeater data
|
|
52
|
+
* Creates 15 repeaters with various tech:fband combinations
|
|
53
|
+
*/
|
|
54
|
+
export const demoRepeaters = REPEATER_POSITIONS.map((pos, idx) => {
|
|
55
|
+
const config = REPEATER_CONFIGS[idx];
|
|
56
|
+
const repeaterId = `REP-${String(idx + 1).padStart(3, '0')}`;
|
|
57
|
+
const donorCellId = `CELL-${String((idx * 7) % 100).padStart(3, '0')}`;
|
|
58
|
+
return {
|
|
59
|
+
repeaterId,
|
|
60
|
+
donorCellId,
|
|
61
|
+
donorCellName: `Cell-${config.tech}-${config.fband}-${idx + 1}`,
|
|
62
|
+
latitude: pos.lat,
|
|
63
|
+
longitude: pos.lng,
|
|
64
|
+
azimuth: config.azimuth,
|
|
65
|
+
beamwidth: 30, // Fixed 30° beamwidth
|
|
66
|
+
tech: config.tech,
|
|
67
|
+
fband: config.fband,
|
|
68
|
+
type: 'REPEATER',
|
|
69
|
+
height: 15 + (idx % 5) * 5, // Heights between 15-35m
|
|
70
|
+
factory_nbr: `FN-${String(1000 + idx).padStart(6, '0')}`,
|
|
71
|
+
provider: idx % 2 === 0 ? 'Operator-A' : 'Operator-B',
|
|
72
|
+
featureGroup: config.tech === '5G' ? 'High-Priority' : config.tech === '4G' ? 'Standard' : 'Legacy'
|
|
73
|
+
};
|
|
74
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Radius multipliers for different repeater tech-band combinations
|
|
3
|
+
*
|
|
4
|
+
* These multipliers adjust the visual size of repeater arcs based on technology and frequency.
|
|
5
|
+
* Lower frequencies typically have larger coverage areas.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Tech:FBand key type for repeaters
|
|
9
|
+
*/
|
|
10
|
+
export type RepeaterTechBandKey = string;
|
|
11
|
+
/**
|
|
12
|
+
* Radius multipliers by tech:fband
|
|
13
|
+
* Multiplier is applied to base radius to calculate final arc size
|
|
14
|
+
*
|
|
15
|
+
* Lower frequency = larger multiplier (wider coverage)
|
|
16
|
+
* Higher frequency = smaller multiplier (more focused coverage)
|
|
17
|
+
*/
|
|
18
|
+
export declare const REPEATER_RADIUS_MULTIPLIER: Record<string, number>;
|
|
19
|
+
/**
|
|
20
|
+
* Get radius multiplier for a tech:fband combination
|
|
21
|
+
*
|
|
22
|
+
* @param tech - Technology (2G, 4G, 5G)
|
|
23
|
+
* @param fband - Frequency band (e.g., "LTE1800", "GSM900")
|
|
24
|
+
* @returns Radius multiplier (defaults to 1.0 if not found)
|
|
25
|
+
*/
|
|
26
|
+
export declare function getRepeaterRadiusMultiplier(tech: string, fband: string): number;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Radius multipliers for different repeater tech-band combinations
|
|
3
|
+
*
|
|
4
|
+
* These multipliers adjust the visual size of repeater arcs based on technology and frequency.
|
|
5
|
+
* Lower frequencies typically have larger coverage areas.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Radius multipliers by tech:fband
|
|
9
|
+
* Multiplier is applied to base radius to calculate final arc size
|
|
10
|
+
*
|
|
11
|
+
* Lower frequency = larger multiplier (wider coverage)
|
|
12
|
+
* Higher frequency = smaller multiplier (more focused coverage)
|
|
13
|
+
*/
|
|
14
|
+
export const REPEATER_RADIUS_MULTIPLIER = {
|
|
15
|
+
// 2G bands - largest coverage
|
|
16
|
+
'2G:GSM900': 1.2,
|
|
17
|
+
'2G:GSM1800': 1.0,
|
|
18
|
+
// 4G bands - medium coverage
|
|
19
|
+
'4G:LTE700': 1.3,
|
|
20
|
+
'4G:LTE800': 1.2,
|
|
21
|
+
'4G:LTE900': 1.1,
|
|
22
|
+
'4G:LTE1800': 0.8,
|
|
23
|
+
'4G:LTE2100': 0.7,
|
|
24
|
+
'4G:LTE2600': 0.6,
|
|
25
|
+
// 5G bands - smallest coverage (higher frequencies)
|
|
26
|
+
'5G:5G-700': 1.2,
|
|
27
|
+
'5G:5G-2100': 0.7,
|
|
28
|
+
'5G:5G-3500': 0.5,
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Get radius multiplier for a tech:fband combination
|
|
32
|
+
*
|
|
33
|
+
* @param tech - Technology (2G, 4G, 5G)
|
|
34
|
+
* @param fband - Frequency band (e.g., "LTE1800", "GSM900")
|
|
35
|
+
* @returns Radius multiplier (defaults to 1.0 if not found)
|
|
36
|
+
*/
|
|
37
|
+
export function getRepeaterRadiusMultiplier(tech, fband) {
|
|
38
|
+
const key = `${tech}:${fband}`;
|
|
39
|
+
return REPEATER_RADIUS_MULTIPLIER[key] ?? 1.0; // Default to 1.0x if unknown
|
|
40
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Z-Index ordering for repeater tech-band combinations
|
|
3
|
+
*
|
|
4
|
+
* Higher frequency bands are rendered on top when repeaters overlap.
|
|
5
|
+
* This follows the same pattern as cells.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Z-index ordering for tech:fband combinations
|
|
9
|
+
* Higher number = rendered on top
|
|
10
|
+
*
|
|
11
|
+
* Organized by frequency (low to high):
|
|
12
|
+
* - Lower frequencies (700-900) at bottom
|
|
13
|
+
* - Mid frequencies (1800-2100) in middle
|
|
14
|
+
* - Higher frequencies (2600-3500) on top
|
|
15
|
+
*/
|
|
16
|
+
export declare const REPEATER_TECH_BAND_Z_ORDER: Record<string, number>;
|
|
17
|
+
/**
|
|
18
|
+
* Get z-order index for a tech:fband combination
|
|
19
|
+
* Used to set the sort key for layering overlapping repeaters
|
|
20
|
+
*
|
|
21
|
+
* @param tech - Technology (2G, 4G, 5G)
|
|
22
|
+
* @param fband - Frequency band (e.g., "LTE1800", "GSM900")
|
|
23
|
+
* @returns Z-order index (defaults to 0 if not found)
|
|
24
|
+
*/
|
|
25
|
+
export declare function getRepeaterZOrder(tech: string, fband: string): number;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Z-Index ordering for repeater tech-band combinations
|
|
3
|
+
*
|
|
4
|
+
* Higher frequency bands are rendered on top when repeaters overlap.
|
|
5
|
+
* This follows the same pattern as cells.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Z-index ordering for tech:fband combinations
|
|
9
|
+
* Higher number = rendered on top
|
|
10
|
+
*
|
|
11
|
+
* Organized by frequency (low to high):
|
|
12
|
+
* - Lower frequencies (700-900) at bottom
|
|
13
|
+
* - Mid frequencies (1800-2100) in middle
|
|
14
|
+
* - Higher frequencies (2600-3500) on top
|
|
15
|
+
*/
|
|
16
|
+
export const REPEATER_TECH_BAND_Z_ORDER = {
|
|
17
|
+
// 2G bands (lowest)
|
|
18
|
+
'2G:GSM900': 1,
|
|
19
|
+
'2G:GSM1800': 2,
|
|
20
|
+
// 4G bands (medium)
|
|
21
|
+
'4G:LTE700': 3,
|
|
22
|
+
'4G:LTE800': 4,
|
|
23
|
+
'4G:LTE900': 5,
|
|
24
|
+
'4G:LTE1800': 6,
|
|
25
|
+
'4G:LTE2100': 7,
|
|
26
|
+
'4G:LTE2600': 8,
|
|
27
|
+
// 5G bands (highest)
|
|
28
|
+
'5G:5G-700': 9,
|
|
29
|
+
'5G:5G-2100': 10,
|
|
30
|
+
'5G:5G-3500': 11,
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Get z-order index for a tech:fband combination
|
|
34
|
+
* Used to set the sort key for layering overlapping repeaters
|
|
35
|
+
*
|
|
36
|
+
* @param tech - Technology (2G, 4G, 5G)
|
|
37
|
+
* @param fband - Frequency band (e.g., "LTE1800", "GSM900")
|
|
38
|
+
* @returns Z-order index (defaults to 0 if not found)
|
|
39
|
+
*/
|
|
40
|
+
export function getRepeaterZOrder(tech, fband) {
|
|
41
|
+
const key = `${tech}:${fband}`;
|
|
42
|
+
return REPEATER_TECH_BAND_Z_ORDER[key] ?? 0;
|
|
43
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Z-Index constants for repeater layers
|
|
3
|
+
*
|
|
4
|
+
* Repeaters are rendered above cells but below labels
|
|
5
|
+
*/
|
|
6
|
+
/** Fill layer z-index for repeater arcs */
|
|
7
|
+
export declare const REPEATER_FILL_Z_INDEX = 100;
|
|
8
|
+
/** Line layer z-index for repeater arc borders */
|
|
9
|
+
export declare const REPEATER_LINE_Z_INDEX = 101;
|
|
10
|
+
/** Label layer z-index for repeater labels */
|
|
11
|
+
export declare const REPEATER_LABEL_Z_INDEX = 102;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Z-Index constants for repeater layers
|
|
3
|
+
*
|
|
4
|
+
* Repeaters are rendered above cells but below labels
|
|
5
|
+
*/
|
|
6
|
+
/** Fill layer z-index for repeater arcs */
|
|
7
|
+
export const REPEATER_FILL_Z_INDEX = 100;
|
|
8
|
+
/** Line layer z-index for repeater arc borders */
|
|
9
|
+
export const REPEATER_LINE_Z_INDEX = 101;
|
|
10
|
+
/** Label layer z-index for repeater labels */
|
|
11
|
+
export const REPEATER_LABEL_Z_INDEX = 102;
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* RepeaterFilterControl - Tech:FBand hierarchical filter control for repeaters
|
|
4
|
+
*
|
|
5
|
+
* Features:
|
|
6
|
+
* - Fixed Tech→FBand two-level tree structure
|
|
7
|
+
* - TreeView with checkboxes for filtering
|
|
8
|
+
* - Color pickers on leaf nodes (tech:fband level customization)
|
|
9
|
+
* - Persists tree state to localStorage
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { onMount } from 'svelte';
|
|
13
|
+
import type { Writable } from 'svelte/store';
|
|
14
|
+
import MapControl from '../../../shared/controls/MapControl.svelte';
|
|
15
|
+
import TreeView from '../../../../core/TreeView/TreeView.svelte';
|
|
16
|
+
import { createTreeStore } from '../../../../core/TreeView/tree.store';
|
|
17
|
+
import { buildRepeaterTree, getFilteredRepeaters } from '../utils/repeaterTree';
|
|
18
|
+
import type { RepeaterStoreContext } from '../stores/repeaterStoreContext.svelte';
|
|
19
|
+
import type { TreeStoreValue } from '../../../../core/TreeView/tree.model';
|
|
20
|
+
|
|
21
|
+
interface Props {
|
|
22
|
+
/** Repeater store context */
|
|
23
|
+
store: RepeaterStoreContext;
|
|
24
|
+
/** Control position */
|
|
25
|
+
position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
|
|
26
|
+
/** Control title */
|
|
27
|
+
title?: string;
|
|
28
|
+
/** Optional header icon */
|
|
29
|
+
icon?: string;
|
|
30
|
+
/** Show icon when collapsed (default: true) */
|
|
31
|
+
iconOnlyWhenCollapsed?: boolean;
|
|
32
|
+
/** Initially collapsed? */
|
|
33
|
+
initiallyCollapsed?: boolean;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
let {
|
|
37
|
+
store,
|
|
38
|
+
position = 'top-left',
|
|
39
|
+
title = 'Repeater Filter',
|
|
40
|
+
icon = 'broadcast',
|
|
41
|
+
iconOnlyWhenCollapsed = true,
|
|
42
|
+
initiallyCollapsed = true
|
|
43
|
+
}: Props = $props();
|
|
44
|
+
|
|
45
|
+
let treeStore = $state<Writable<TreeStoreValue> | null>(null);
|
|
46
|
+
|
|
47
|
+
// Rebuild tree when repeaters change
|
|
48
|
+
function rebuildTree() {
|
|
49
|
+
// Build tree from all repeaters (not filtered)
|
|
50
|
+
const treeRoot = buildRepeaterTree(store.repeaters, store.techBandColorMap);
|
|
51
|
+
|
|
52
|
+
// Create or recreate tree store with persistence
|
|
53
|
+
treeStore = createTreeStore({
|
|
54
|
+
nodes: [treeRoot], // TreeView expects array of root nodes
|
|
55
|
+
namespace: 'cellular-repeater-filter',
|
|
56
|
+
persistState: true,
|
|
57
|
+
defaultExpandAll: false
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// Subscribe to tree changes and update filtered repeaters
|
|
61
|
+
if (treeStore) {
|
|
62
|
+
const unsub = treeStore.subscribe((treeValue: TreeStoreValue) => {
|
|
63
|
+
const checkedPaths = treeValue.getCheckedPaths();
|
|
64
|
+
|
|
65
|
+
// Convert string[] to Set<string>
|
|
66
|
+
const checkedPathsSet = new Set(checkedPaths);
|
|
67
|
+
const selectedTechBands = getFilteredRepeaters(checkedPathsSet);
|
|
68
|
+
|
|
69
|
+
// Update visibility for each tech:fband combination
|
|
70
|
+
// First, get all tech:fband combinations from repeaters
|
|
71
|
+
const allTechBands = new Set<string>();
|
|
72
|
+
for (const repeater of store.repeaters) {
|
|
73
|
+
const key = `${repeater.tech}:${repeater.fband}`;
|
|
74
|
+
allTechBands.add(key);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// If only root checked, show all
|
|
78
|
+
if (selectedTechBands.size === 0 && checkedPathsSet.has('root')) {
|
|
79
|
+
// All visible - no toggle needed
|
|
80
|
+
} else {
|
|
81
|
+
// Toggle visibility based on selection
|
|
82
|
+
for (const techBand of allTechBands) {
|
|
83
|
+
const [tech, fband] = techBand.split(':');
|
|
84
|
+
const shouldBeVisible = selectedTechBands.has(techBand);
|
|
85
|
+
const isVisible = store.isTechBandVisible(tech, fband);
|
|
86
|
+
|
|
87
|
+
// Only toggle if state needs to change
|
|
88
|
+
if (shouldBeVisible !== isVisible) {
|
|
89
|
+
store.toggleTechBand(tech, fband);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
return () => unsub();
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Handle color changes from tree
|
|
100
|
+
function handleColorChange(nodeId: string, color: string) {
|
|
101
|
+
// Extract tech:fband from path (format: "root:tech:fband")
|
|
102
|
+
const parts = nodeId.split(':');
|
|
103
|
+
if (parts.length >= 3) {
|
|
104
|
+
const tech = parts[1];
|
|
105
|
+
const fband = parts[2];
|
|
106
|
+
store.setTechBandColor(tech, fband, color);
|
|
107
|
+
|
|
108
|
+
// Rebuild tree to reflect new color
|
|
109
|
+
rebuildTree();
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Initialize on mount
|
|
114
|
+
onMount(() => {
|
|
115
|
+
rebuildTree();
|
|
116
|
+
});
|
|
117
|
+
</script>
|
|
118
|
+
|
|
119
|
+
<MapControl {position} {title} {icon} {iconOnlyWhenCollapsed} collapsible={true} {initiallyCollapsed}>
|
|
120
|
+
{#if treeStore && $treeStore}
|
|
121
|
+
<div class="repeater-filter-tree">
|
|
122
|
+
<TreeView store={$treeStore!} showControls={false}>
|
|
123
|
+
{#snippet children({ node, state })}
|
|
124
|
+
<!-- Color picker for leaf nodes (tech:fband combinations) -->
|
|
125
|
+
{#if node.metadata?.color}
|
|
126
|
+
<input
|
|
127
|
+
type="color"
|
|
128
|
+
class="color-picker"
|
|
129
|
+
value={node.metadata.color}
|
|
130
|
+
oninput={(e) => handleColorChange(node.path, e.currentTarget.value)}
|
|
131
|
+
onclick={(e) => e.stopPropagation()}
|
|
132
|
+
title="Choose color for {node.label}"
|
|
133
|
+
/>
|
|
134
|
+
{/if}
|
|
135
|
+
{/snippet}
|
|
136
|
+
</TreeView>
|
|
137
|
+
|
|
138
|
+
<div class="repeater-filter-stats">
|
|
139
|
+
<small class="text-muted">
|
|
140
|
+
Showing {store.filteredRepeaters.length} of {store.repeaters.length} repeaters
|
|
141
|
+
</small>
|
|
142
|
+
</div>
|
|
143
|
+
</div>
|
|
144
|
+
{:else}
|
|
145
|
+
<div class="text-muted text-center p-3" style="font-size: 0.875rem;">
|
|
146
|
+
Loading tree...
|
|
147
|
+
</div>
|
|
148
|
+
{/if}
|
|
149
|
+
</MapControl>
|
|
150
|
+
|
|
151
|
+
<style>
|
|
152
|
+
.repeater-filter-tree {
|
|
153
|
+
max-height: 400px;
|
|
154
|
+
overflow-y: auto;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.color-picker {
|
|
158
|
+
width: 24px;
|
|
159
|
+
height: 24px;
|
|
160
|
+
border: none;
|
|
161
|
+
border-radius: 3px;
|
|
162
|
+
cursor: pointer;
|
|
163
|
+
margin-left: 8px;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
.repeater-filter-stats {
|
|
167
|
+
margin-top: 0.5rem;
|
|
168
|
+
padding-top: 0.5rem;
|
|
169
|
+
border-top: 1px solid var(--bs-border-color);
|
|
170
|
+
text-align: center;
|
|
171
|
+
}
|
|
172
|
+
</style>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { RepeaterStoreContext } from '../stores/repeaterStoreContext.svelte';
|
|
2
|
+
interface Props {
|
|
3
|
+
/** Repeater store context */
|
|
4
|
+
store: RepeaterStoreContext;
|
|
5
|
+
/** Control position */
|
|
6
|
+
position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
|
|
7
|
+
/** Control title */
|
|
8
|
+
title?: string;
|
|
9
|
+
/** Optional header icon */
|
|
10
|
+
icon?: string;
|
|
11
|
+
/** Show icon when collapsed (default: true) */
|
|
12
|
+
iconOnlyWhenCollapsed?: boolean;
|
|
13
|
+
/** Initially collapsed? */
|
|
14
|
+
initiallyCollapsed?: boolean;
|
|
15
|
+
}
|
|
16
|
+
declare const RepeaterFilterControl: import("svelte").Component<Props, {}, "">;
|
|
17
|
+
type RepeaterFilterControl = ReturnType<typeof RepeaterFilterControl>;
|
|
18
|
+
export default RepeaterFilterControl;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Repeaters Feature - Public API exports
|
|
3
|
+
*
|
|
4
|
+
* Barrel export for all public-facing repeater functionality
|
|
5
|
+
*/
|
|
6
|
+
export type { Repeater, RepeaterTreeNode } from './types';
|
|
7
|
+
export type { RepeaterStoreValue, RepeaterStoreContext } from './stores/repeaterStoreContext.svelte';
|
|
8
|
+
export { createRepeaterStoreContext } from './stores/repeaterStoreContext.svelte';
|
|
9
|
+
export { default as RepeatersLayer } from './layers/RepeatersLayer.svelte';
|
|
10
|
+
export { default as RepeaterLabelsLayer } from './layers/RepeaterLabelsLayer.svelte';
|
|
11
|
+
export { default as RepeaterFilterControl } from './controls/RepeaterFilterControl.svelte';
|
|
12
|
+
export { repeatersToGeoJSON } from './utils/repeaterGeoJSON';
|
|
13
|
+
export { buildRepeaterTree, getFilteredRepeaters } from './utils/repeaterTree';
|
|
14
|
+
export { REPEATER_FILL_Z_INDEX, REPEATER_LINE_Z_INDEX, REPEATER_LABEL_Z_INDEX } from './constants/zIndex';
|
|
15
|
+
export { REPEATER_RADIUS_MULTIPLIER, getRepeaterRadiusMultiplier } from './constants/radiusMultipliers';
|
|
16
|
+
export { REPEATER_TECH_BAND_Z_ORDER, getRepeaterZOrder } from './constants/techBandZOrder';
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Repeaters Feature - Public API exports
|
|
3
|
+
*
|
|
4
|
+
* Barrel export for all public-facing repeater functionality
|
|
5
|
+
*/
|
|
6
|
+
// Store
|
|
7
|
+
export { createRepeaterStoreContext } from './stores/repeaterStoreContext.svelte';
|
|
8
|
+
// Layers
|
|
9
|
+
export { default as RepeatersLayer } from './layers/RepeatersLayer.svelte';
|
|
10
|
+
export { default as RepeaterLabelsLayer } from './layers/RepeaterLabelsLayer.svelte';
|
|
11
|
+
// Controls
|
|
12
|
+
export { default as RepeaterFilterControl } from './controls/RepeaterFilterControl.svelte';
|
|
13
|
+
// Utilities
|
|
14
|
+
export { repeatersToGeoJSON } from './utils/repeaterGeoJSON';
|
|
15
|
+
export { buildRepeaterTree, getFilteredRepeaters } from './utils/repeaterTree';
|
|
16
|
+
// Constants
|
|
17
|
+
export { REPEATER_FILL_Z_INDEX, REPEATER_LINE_Z_INDEX, REPEATER_LABEL_Z_INDEX } from './constants/zIndex';
|
|
18
|
+
export { REPEATER_RADIUS_MULTIPLIER, getRepeaterRadiusMultiplier } from './constants/radiusMultipliers';
|
|
19
|
+
export { REPEATER_TECH_BAND_Z_ORDER, getRepeaterZOrder } from './constants/techBandZOrder';
|