@smartnet360/svelte-components 0.0.87 → 0.0.88

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.
Files changed (32) hide show
  1. package/dist/map-v3/core/components/MapStoreBridge.svelte +25 -0
  2. package/dist/map-v3/core/components/MapStoreBridge.svelte.d.ts +8 -0
  3. package/dist/map-v3/core/index.d.ts +1 -0
  4. package/dist/map-v3/core/index.js +1 -0
  5. package/dist/map-v3/demo/DemoMap.svelte +10 -0
  6. package/dist/map-v3/features/cells/components/CellFilterControl.svelte +24 -6
  7. package/dist/map-v3/features/cells/components/CellFilterControl.svelte.d.ts +3 -0
  8. package/dist/map-v3/features/cells/constants/statusStyles.d.ts +14 -0
  9. package/dist/map-v3/features/cells/constants/statusStyles.js +49 -0
  10. package/dist/map-v3/features/cells/constants.js +27 -13
  11. package/dist/map-v3/features/cells/layers/CellLabelsLayer.svelte +7 -2
  12. package/dist/map-v3/features/cells/layers/CellsLayer.svelte +16 -7
  13. package/dist/map-v3/features/cells/logic/geometry.js +22 -2
  14. package/dist/map-v3/features/repeaters/components/RepeaterFilterControl.svelte +14 -0
  15. package/dist/map-v3/features/repeaters/layers/RepeaterLabelsLayer.svelte +57 -26
  16. package/dist/map-v3/features/selection/components/FeatureSelectionControl.svelte +309 -0
  17. package/dist/map-v3/features/selection/components/FeatureSelectionControl.svelte.d.ts +20 -0
  18. package/dist/map-v3/features/selection/index.d.ts +9 -0
  19. package/dist/map-v3/features/selection/index.js +10 -0
  20. package/dist/map-v3/features/selection/layers/SelectionHighlightLayers.svelte +209 -0
  21. package/dist/map-v3/features/selection/layers/SelectionHighlightLayers.svelte.d.ts +13 -0
  22. package/dist/map-v3/features/selection/stores/selection.store.svelte.d.ts +84 -0
  23. package/dist/map-v3/features/selection/stores/selection.store.svelte.js +174 -0
  24. package/dist/map-v3/features/selection/types.d.ts +25 -0
  25. package/dist/map-v3/features/selection/types.js +4 -0
  26. package/dist/map-v3/features/sites/components/SiteFilterControl.svelte +14 -0
  27. package/dist/map-v3/features/sites/layers/SiteLabelsLayer.svelte +57 -26
  28. package/dist/map-v3/index.d.ts +1 -0
  29. package/dist/map-v3/index.js +2 -0
  30. package/dist/map-v3/shared/controls/MapControl.svelte +37 -10
  31. package/dist/map-v3/shared/controls/MapControl.svelte.d.ts +2 -0
  32. package/package.json +1 -1
@@ -0,0 +1,25 @@
1
+ <script lang="ts">
2
+ import { getContext } from 'svelte';
3
+ import type { MapStore } from '../stores/map.store.svelte';
4
+ import type { Map as MapboxMap } from 'mapbox-gl';
5
+
6
+ interface Props {
7
+ onMapChange?: (map: MapboxMap | null) => void;
8
+ children?: import('svelte').Snippet;
9
+ }
10
+
11
+ let { onMapChange, children }: Props = $props();
12
+
13
+ const mapStore = getContext<MapStore>('MAP_CONTEXT');
14
+ let ready = $state(false);
15
+
16
+ $effect(() => {
17
+ const map = mapStore.map;
18
+ onMapChange?.(map);
19
+ ready = map !== null;
20
+ });
21
+ </script>
22
+
23
+ {#if ready && children}
24
+ {@render children()}
25
+ {/if}
@@ -0,0 +1,8 @@
1
+ import type { Map as MapboxMap } from 'mapbox-gl';
2
+ interface Props {
3
+ onMapChange?: (map: MapboxMap | null) => void;
4
+ children?: import('svelte').Snippet;
5
+ }
6
+ declare const MapStoreBridge: import("svelte").Component<Props, {}, "">;
7
+ type MapStoreBridge = ReturnType<typeof MapStoreBridge>;
8
+ export default MapStoreBridge;
@@ -2,5 +2,6 @@ export { MapStore, createMapStore } from './stores/map.store.svelte';
2
2
  export type { ViewportStore, ViewportState } from './stores/viewport.store.svelte';
3
3
  export { createViewportStore } from './stores/viewport.store.svelte';
4
4
  export { default as Map } from './components/Map.svelte';
5
+ export { default as MapStoreBridge } from './components/MapStoreBridge.svelte';
5
6
  export { default as MapStyleControl } from './controls/MapStyleControl.svelte';
6
7
  export { default as FeatureSettingsControl } from './controls/FeatureSettingsControl.svelte';
@@ -1,5 +1,6 @@
1
1
  export { MapStore, createMapStore } from './stores/map.store.svelte';
2
2
  export { createViewportStore } from './stores/viewport.store.svelte';
3
3
  export { default as Map } from './components/Map.svelte';
4
+ export { default as MapStoreBridge } from './components/MapStoreBridge.svelte';
4
5
  export { default as MapStyleControl } from './controls/MapStyleControl.svelte';
5
6
  export { default as FeatureSettingsControl } from './controls/FeatureSettingsControl.svelte';
@@ -19,6 +19,7 @@
19
19
  import SiteLabelsLayer from '../features/sites/layers/SiteLabelsLayer.svelte';
20
20
  import SiteFilterControl from '../features/sites/components/SiteFilterControl.svelte';
21
21
  import { createSiteRegistry } from '../features/sites/stores/site.registry.svelte';
22
+ import FeatureSelectionControl from '../features/selection/components/FeatureSelectionControl.svelte';
22
23
  import { demoCells } from './demo-cells';
23
24
  import { demoRepeaters } from './demo-repeaters';
24
25
 
@@ -93,6 +94,15 @@
93
94
  <CellLabelsLayer dataStore={cellData} registry={cellRegistry} displayStore={cellDisplay} />
94
95
  <RepeatersLayer dataStore={repeaterData} registry={repeaterRegistry} displayStore={repeaterDisplay} />
95
96
  <RepeaterLabelsLayer dataStore={repeaterData} registry={repeaterRegistry} displayStore={repeaterDisplay} />
97
+
98
+ <FeatureSelectionControl
99
+ position="bottom-left"
100
+ cellDataStore={cellData}
101
+ cellDisplayStore={cellDisplay}
102
+ title="Feature Selection"
103
+ actionButtonLabel="Export Selected"
104
+ onAction={(ids) => console.log('Selected features:', ids)}
105
+ />
96
106
  </Map>
97
107
  </div>
98
108
 
@@ -14,17 +14,25 @@
14
14
  registry: CellRegistry;
15
15
  displayStore: CellDisplayStore;
16
16
  position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
17
+ level1Options?: CellGroupingField[];
18
+ level2Options?: CellGroupingField[];
17
19
  }
18
20
 
19
- let { dataStore, registry, displayStore, position = 'top-left' }: Props = $props();
21
+ let {
22
+ dataStore,
23
+ registry,
24
+ displayStore,
25
+ position = 'top-left',
26
+ level1Options = ['tech', 'status', 'siteId'],
27
+ level2Options = ['fband', 'frq', 'status', 'none']
28
+ }: Props = $props();
20
29
 
21
30
  // Local State for Grouping
22
- // let level1 = $state<CellGroupingField>('tech');
23
- // let level2 = $state<CellGroupingField>('fband');
24
31
  let includePlanned = $state(false);
25
32
 
26
- const level1Options: CellGroupingField[] = ['tech', 'status', 'siteId'];
27
- const level2Options: CellGroupingField[] = ['fband', 'frq', 'status', 'none'];
33
+ function toggleLabels() {
34
+ displayStore.showLabels = !displayStore.showLabels;
35
+ }
28
36
 
29
37
  // Sync includePlanned with dataStore
30
38
  $effect(() => {
@@ -106,7 +114,17 @@
106
114
  }
107
115
  </script>
108
116
 
109
- <MapControl {position} title="Layers" icon="radioactive" controlWidth="320px">
117
+ <MapControl {position} title="Cells" icon="grid" controlWidth="320px">
118
+ {#snippet actions()}
119
+ <button
120
+ class="btn btn-sm btn-outline-secondary border-0 p-1 px-2"
121
+ title={displayStore.showLabels ? 'Hide Labels' : 'Show Labels'}
122
+ aria-label={displayStore.showLabels ? 'Hide Labels' : 'Show Labels'}
123
+ onclick={toggleLabels}
124
+ >
125
+ <i class="bi bi-tag{displayStore.showLabels ? '-fill' : ''}"></i>
126
+ </button>
127
+ {/snippet}
110
128
  <div class="cell-filter-control">
111
129
  <!-- Status Filter Checkbox -->
112
130
  <div class="mb-3 px-1">
@@ -1,11 +1,14 @@
1
1
  import type { CellDataStore } from '../stores/cell.data.svelte';
2
2
  import type { CellRegistry } from '../stores/cell.registry.svelte';
3
3
  import type { CellDisplayStore } from '../stores/cell.display.svelte';
4
+ import type { CellGroupingField } from '../types';
4
5
  interface Props {
5
6
  dataStore: CellDataStore;
6
7
  registry: CellRegistry;
7
8
  displayStore: CellDisplayStore;
8
9
  position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
10
+ level1Options?: CellGroupingField[];
11
+ level2Options?: CellGroupingField[];
9
12
  }
10
13
  declare const CellFilterControl: import("svelte").Component<Props, {}, "">;
11
14
  type CellFilterControl = ReturnType<typeof CellFilterControl>;
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Default Status Styles
3
+ *
4
+ * Visual styling for cell sector borders based on status
5
+ */
6
+ import type { CellStatus } from '../types';
7
+ export interface CellStatusStyle {
8
+ lineType: 'solid' | 'dashed' | 'dotted';
9
+ lineWidth: number;
10
+ lineColor: string;
11
+ opacity: number;
12
+ dashArray: number[];
13
+ }
14
+ export declare const DEFAULT_STATUS_STYLES: Record<CellStatus, CellStatusStyle>;
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Default Status Styles
3
+ *
4
+ * Visual styling for cell sector borders based on status
5
+ */
6
+ export const DEFAULT_STATUS_STYLES = {
7
+ On_Air: {
8
+ lineType: 'solid',
9
+ lineWidth: 2,
10
+ lineColor: '#000000',
11
+ opacity: 1.0,
12
+ dashArray: []
13
+ },
14
+ On_Air_UNDER_CONSTRUCTION: {
15
+ lineType: 'solid',
16
+ lineWidth: 2,
17
+ lineColor: '#FFA500',
18
+ opacity: 0.8,
19
+ dashArray: []
20
+ },
21
+ On_Air_Locked: {
22
+ lineType: 'solid',
23
+ lineWidth: 2,
24
+ lineColor: '#FF0000',
25
+ opacity: 0.9,
26
+ dashArray: []
27
+ },
28
+ RF_Plan_Ready: {
29
+ lineType: 'dashed',
30
+ lineWidth: 2,
31
+ lineColor: '#666666',
32
+ opacity: 0.6,
33
+ dashArray: [5, 5]
34
+ },
35
+ 'Re-Planned_RF_Plan_Ready': {
36
+ lineType: 'dashed',
37
+ lineWidth: 2,
38
+ lineColor: '#888888',
39
+ opacity: 0.5,
40
+ dashArray: [5, 5]
41
+ },
42
+ Tavlati_RF_Plan_Ready: {
43
+ lineType: 'dashed',
44
+ lineWidth: 2,
45
+ lineColor: '#999999',
46
+ opacity: 0.4,
47
+ dashArray: [5, 5]
48
+ }
49
+ };
@@ -5,20 +5,34 @@
5
5
  * Higher frequency bands typically rendered on top
6
6
  */
7
7
  export const Z_INDEX_BY_BAND = {
8
- // 2G bands
9
- '2G_900': 3,
10
- '2G_1800': 7,
11
- // 4G bands
8
+ //low
12
9
  '4G_700': 0,
13
- '4G_800': 2,
14
- '4G_900': 4,
15
- '4G_1800': 8,
16
- '4G_2100': 9,
17
- '4G_2600': 11,
18
- // 5G bands
19
- '5G_700': 1,
20
- '5G_2100': 10,
21
- '5G_3500': 14
10
+ '5G_700': 2,
11
+ '4G_800': 4,
12
+ '2G_900': 6,
13
+ '4G_900': 8,
14
+ //mid
15
+ '2G_1800': 10,
16
+ '4G_1800': 12,
17
+ '4G_2100': 14,
18
+ '5G_2100': 16,
19
+ '4G_2600': 18,
20
+ //high
21
+ '5G_3500': 20,
22
+ // //low
23
+ // '4G_700': 0,
24
+ // '5G_700': 2,
25
+ // '4G_800': 4,
26
+ // '2G_900': 6,
27
+ // '4G_900': 8,
28
+ // //mid
29
+ // '2G_1800': 14,
30
+ // '4G_1800': 16,
31
+ // '4G_2100': 18,
32
+ // '5G_2100': 20,
33
+ // '4G_2600': 22,
34
+ // //high
35
+ // '5G_3500': 28,
22
36
  };
23
37
  /**
24
38
  * Base Z-Index for Mapbox layer ordering
@@ -214,13 +214,18 @@
214
214
  }
215
215
  };
216
216
 
217
+ const handleStyleLoad = () => {
218
+ addLayers();
219
+ updateLayer();
220
+ };
221
+
217
222
  addLayers();
218
- map.on('style.load', addLayers);
223
+ map.on('style.load', handleStyleLoad);
219
224
  map.on('moveend', updateLayer);
220
225
  map.on('zoomend', updateLayer);
221
226
 
222
227
  return () => {
223
- map.off('style.load', addLayers);
228
+ map.off('style.load', handleStyleLoad);
224
229
  map.off('moveend', updateLayer);
225
230
  map.off('zoomend', updateLayer);
226
231
  if (map.getLayer(layerId)) map.removeLayer(layerId);
@@ -34,9 +34,8 @@
34
34
  if (map.getLayer(layerId)) {
35
35
  map.setPaintProperty(layerId, 'fill-opacity', displayStore.fillOpacity);
36
36
  }
37
- if (map.getLayer(lineLayerId)) {
38
- map.setPaintProperty(lineLayerId, 'line-width', displayStore.lineWidth);
39
- }
37
+ // Note: Line styling is now data-driven from status properties
38
+ // but we can still apply a global multiplier via lineWidth
40
39
  });
41
40
 
42
41
  $effect(() => {
@@ -68,15 +67,25 @@
68
67
  }
69
68
 
70
69
  if (!map.getLayer(lineLayerId)) {
71
- // Line Layer (Border)
70
+ // Line Layer (Border) - Status-based styling
72
71
  map.addLayer({
73
72
  id: lineLayerId,
74
73
  type: 'line',
75
74
  source: sourceId,
76
75
  paint: {
77
- 'line-color': '#000',
78
- 'line-width': displayStore.lineWidth,
79
- 'line-opacity': 0.3
76
+ 'line-color': ['get', 'lineColor'],
77
+ 'line-width': [
78
+ '*',
79
+ ['get', 'lineWidth'],
80
+ displayStore.lineWidth
81
+ ],
82
+ 'line-opacity': ['get', 'lineOpacity'],
83
+ 'line-dasharray': [
84
+ 'case',
85
+ ['>', ['length', ['get', 'dashArray']], 0],
86
+ ['get', 'dashArray'],
87
+ ['literal', []]
88
+ ]
80
89
  }
81
90
  });
82
91
  }
@@ -1,4 +1,5 @@
1
1
  import * as turf from '@turf/turf';
2
+ import { DEFAULT_STATUS_STYLES } from '../constants/statusStyles';
2
3
  /**
3
4
  * Calculates the radius in meters required to achieve a target pixel size
4
5
  * at a specific latitude and zoom level.
@@ -22,14 +23,33 @@ export function generateCellArc(cell, radiusMeters, zIndex, color) {
22
23
  const sector = turf.sector(center, radiusMeters / 1000, bearing1, bearing2, {
23
24
  steps: 10 // Low steps for performance, increase if jagged
24
25
  });
26
+ // Get status style
27
+ const statusStyle = DEFAULT_STATUS_STYLES[cell.status] || DEFAULT_STATUS_STYLES['On_Air'];
25
28
  // Attach properties for styling and interaction
26
29
  sector.properties = {
27
- id: cell.id,
30
+ id: cell.txId,
31
+ txId: cell.txId,
32
+ cellId: cell.txId,
33
+ cellID: cell.cellID,
28
34
  cellName: cell.cellName,
35
+ siteId: cell.siteId,
36
+ latitude: cell.latitude,
37
+ longitude: cell.longitude,
38
+ // Cell attributes
29
39
  tech: cell.tech,
40
+ band: cell.frq,
41
+ status: cell.status,
42
+ type: cell.type,
43
+ azimuth: cell.azimuth,
44
+ beamwidth: cell.beamwidth,
30
45
  fband: cell.fband,
31
46
  zIndex: zIndex,
32
- color: color
47
+ color: color,
48
+ // Status-based line styling
49
+ lineColor: statusStyle.lineColor,
50
+ lineWidth: statusStyle.lineWidth,
51
+ lineOpacity: statusStyle.opacity,
52
+ dashArray: statusStyle.dashArray
33
53
  };
34
54
  return sector;
35
55
  }
@@ -74,9 +74,23 @@
74
74
  };
75
75
  return labels[field] || field;
76
76
  }
77
+
78
+ function toggleLabels() {
79
+ displayStore.showLabels = !displayStore.showLabels;
80
+ }
77
81
  </script>
78
82
 
79
83
  <MapControl {position} title="Repeaters" icon="router" controlWidth="320px">
84
+ {#snippet actions()}
85
+ <button
86
+ class="btn btn-sm btn-outline-secondary border-0 p-1 px-2"
87
+ title={displayStore.showLabels ? 'Hide Labels' : 'Show Labels'}
88
+ aria-label={displayStore.showLabels ? 'Hide Labels' : 'Show Labels'}
89
+ onclick={toggleLabels}
90
+ >
91
+ <i class="bi bi-tag{displayStore.showLabels ? '-fill' : ''}"></i>
92
+ </button>
93
+ {/snippet}
80
94
  <div class="card shadow-sm border-0">
81
95
  <!-- <div class="card-header bg-white py-2 d-flex justify-content-between align-items-center">
82
96
  <h6 class="mb-0 fw-bold text-primary">
@@ -143,35 +143,66 @@
143
143
  features
144
144
  };
145
145
 
146
+ // 4. Update Source
146
147
  const source = map.getSource(sourceId) as mapboxgl.GeoJSONSource;
147
148
  if (source) {
148
149
  source.setData(geojson);
149
- } else {
150
- map.addSource(sourceId, {
151
- type: 'geojson',
152
- data: geojson
153
- });
154
-
155
- map.addLayer({
156
- id: layerId,
157
- type: 'symbol',
158
- source: sourceId,
159
- layout: {
160
- 'text-field': ['get', 'label'],
161
- 'text-font': ['Open Sans Semibold', 'Arial Unicode MS Bold'],
162
- 'text-size': displayStore.labelFontSize,
163
- 'text-offset': ['get', 'offset'],
164
- 'text-anchor': 'center',
165
- 'text-justify': 'auto',
166
- 'text-allow-overlap': false,
167
- 'text-ignore-placement': false
168
- },
169
- paint: {
170
- 'text-color': displayStore.labelColor,
171
- 'text-halo-color': displayStore.labelHaloColor,
172
- 'text-halo-width': displayStore.labelHaloWidth
173
- }
174
- });
175
150
  }
176
151
  }
152
+
153
+ // Initial Setup
154
+ $effect(() => {
155
+ const map = mapStore.map;
156
+ if (!map) return;
157
+
158
+ const addLayers = () => {
159
+ if (!map.getSource(sourceId)) {
160
+ map.addSource(sourceId, {
161
+ type: 'geojson',
162
+ data: { type: 'FeatureCollection', features: [] }
163
+ });
164
+ }
165
+
166
+ if (!map.getLayer(layerId)) {
167
+ map.addLayer({
168
+ id: layerId,
169
+ type: 'symbol',
170
+ source: sourceId,
171
+ layout: {
172
+ 'text-field': ['get', 'label'],
173
+ 'text-font': ['Open Sans Semibold', 'Arial Unicode MS Bold'],
174
+ 'text-size': displayStore.labelFontSize,
175
+ 'text-offset': ['get', 'offset'],
176
+ 'text-anchor': 'center',
177
+ 'text-justify': 'auto',
178
+ 'text-allow-overlap': false,
179
+ 'text-ignore-placement': false
180
+ },
181
+ paint: {
182
+ 'text-color': displayStore.labelColor,
183
+ 'text-halo-color': displayStore.labelHaloColor,
184
+ 'text-halo-width': displayStore.labelHaloWidth
185
+ }
186
+ });
187
+ }
188
+ };
189
+
190
+ const handleStyleLoad = () => {
191
+ addLayers();
192
+ updateLayer();
193
+ };
194
+
195
+ addLayers();
196
+ map.on('style.load', handleStyleLoad);
197
+ map.on('moveend', updateLayer);
198
+ map.on('zoomend', updateLayer);
199
+
200
+ return () => {
201
+ map.off('style.load', handleStyleLoad);
202
+ map.off('moveend', updateLayer);
203
+ map.off('zoomend', updateLayer);
204
+ if (map.getLayer(layerId)) map.removeLayer(layerId);
205
+ if (map.getSource(sourceId)) map.removeSource(sourceId);
206
+ };
207
+ });
177
208
  </script>