@smartnet360/svelte-components 0.0.61 → 0.0.63
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 +30 -4
- package/dist/map-v2/core/providers/MapboxProvider.svelte.d.ts +4 -2
- package/dist/map-v2/demo/demo-cells.js +1 -1
- package/dist/map-v2/features/cells/controls/CellFilterControl.svelte +62 -43
- package/dist/map-v2/features/cells/stores/cellStoreContext.svelte.d.ts +4 -0
- package/dist/map-v2/features/cells/stores/cellStoreContext.svelte.js +15 -0
- package/dist/map-v2/features/cells/utils/cellTree.js +2 -14
- package/package.json +1 -1
|
@@ -16,8 +16,10 @@
|
|
|
16
16
|
import 'mapbox-gl/dist/mapbox-gl.css';
|
|
17
17
|
|
|
18
18
|
interface Props {
|
|
19
|
+
/** External Mapbox map instance (if provided, no new map will be created) */
|
|
20
|
+
externalMap?: MapboxMap;
|
|
19
21
|
/** Mapbox access token */
|
|
20
|
-
accessToken
|
|
22
|
+
accessToken?: string;
|
|
21
23
|
/** Map style URL or style object (default: streets-v12) */
|
|
22
24
|
style?: string | StyleSpecification;
|
|
23
25
|
/** Initial map center [lng, lat] */
|
|
@@ -45,6 +47,7 @@
|
|
|
45
47
|
}
|
|
46
48
|
|
|
47
49
|
let {
|
|
50
|
+
externalMap,
|
|
48
51
|
accessToken,
|
|
49
52
|
style = 'mapbox://styles/mapbox/streets-v12',
|
|
50
53
|
center = [0, 0],
|
|
@@ -64,10 +67,30 @@
|
|
|
64
67
|
const mapStore = createMapStore();
|
|
65
68
|
setContext(MAP_CONTEXT_KEY, mapStore);
|
|
66
69
|
|
|
67
|
-
let mapContainer
|
|
70
|
+
let mapContainer = $state<HTMLDivElement>();
|
|
68
71
|
let map: MapboxMap | null = null;
|
|
72
|
+
let isExternalMap = false;
|
|
69
73
|
|
|
70
74
|
onMount(() => {
|
|
75
|
+
// If external map is provided, use it instead of creating a new one
|
|
76
|
+
if (externalMap) {
|
|
77
|
+
map = externalMap;
|
|
78
|
+
isExternalMap = true;
|
|
79
|
+
|
|
80
|
+
// Set the map in store immediately
|
|
81
|
+
mapStore.set(map);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Otherwise, create a new map instance
|
|
86
|
+
if (!accessToken) {
|
|
87
|
+
throw new Error('MapboxProvider: accessToken is required when not using externalMap');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (!mapContainer) {
|
|
91
|
+
throw new Error('MapboxProvider: map container not found');
|
|
92
|
+
}
|
|
93
|
+
|
|
71
94
|
// Set access token
|
|
72
95
|
mapboxgl.accessToken = accessToken;
|
|
73
96
|
|
|
@@ -117,7 +140,8 @@
|
|
|
117
140
|
});
|
|
118
141
|
|
|
119
142
|
onDestroy(() => {
|
|
120
|
-
if (map)
|
|
143
|
+
// Only remove the map if we created it (not an external map)
|
|
144
|
+
if (map && !isExternalMap) {
|
|
121
145
|
map.remove();
|
|
122
146
|
map = null;
|
|
123
147
|
}
|
|
@@ -125,7 +149,9 @@
|
|
|
125
149
|
});
|
|
126
150
|
</script>
|
|
127
151
|
|
|
128
|
-
|
|
152
|
+
{#if !externalMap}
|
|
153
|
+
<div bind:this={mapContainer} class="mapbox-container {className}"></div>
|
|
154
|
+
{/if}
|
|
129
155
|
|
|
130
156
|
{#if children}
|
|
131
157
|
{@render children()}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import { type StyleSpecification } from 'mapbox-gl';
|
|
1
|
+
import { type Map as MapboxMap, type StyleSpecification } from 'mapbox-gl';
|
|
2
2
|
import 'mapbox-gl/dist/mapbox-gl.css';
|
|
3
3
|
interface Props {
|
|
4
|
+
/** External Mapbox map instance (if provided, no new map will be created) */
|
|
5
|
+
externalMap?: MapboxMap;
|
|
4
6
|
/** Mapbox access token */
|
|
5
|
-
accessToken
|
|
7
|
+
accessToken?: string;
|
|
6
8
|
/** Map style URL or style object (default: streets-v12) */
|
|
7
9
|
style?: string | StyleSpecification;
|
|
8
10
|
/** Initial map center [lng, lat] */
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
const BASE_LAT = 37.7749;
|
|
11
11
|
const BASE_LNG = -122.4194;
|
|
12
12
|
// Grid parameters for distributing sites
|
|
13
|
-
const NUM_SITES =
|
|
13
|
+
const NUM_SITES = 10;
|
|
14
14
|
const GRID_SIZE = 10; // 10×10 grid
|
|
15
15
|
const LAT_SPACING = 0.01; // ~1.1 km spacing
|
|
16
16
|
const LNG_SPACING = 0.015; // ~1.1 km spacing (adjusted for longitude)
|
|
@@ -64,13 +64,9 @@
|
|
|
64
64
|
*/
|
|
65
65
|
function validateStoredConfig() {
|
|
66
66
|
const storedConfigStr = localStorage.getItem(STORAGE_CONFIG_KEY);
|
|
67
|
-
const currentConfig = `${level1}:${level2}`;
|
|
67
|
+
const currentConfig = `${level1}:${level2}:${store.includePlannedCells}`;
|
|
68
68
|
|
|
69
69
|
if (storedConfigStr !== currentConfig) {
|
|
70
|
-
console.log('CellFilterControl: Config changed, clearing tree state', {
|
|
71
|
-
stored: storedConfigStr,
|
|
72
|
-
current: currentConfig
|
|
73
|
-
});
|
|
74
70
|
// Clear tree state from localStorage
|
|
75
71
|
localStorage.removeItem('cellular-cell-filter:checked');
|
|
76
72
|
localStorage.removeItem('cellular-cell-filter:expanded');
|
|
@@ -81,15 +77,13 @@
|
|
|
81
77
|
|
|
82
78
|
// Rebuild tree when grouping config or cells change
|
|
83
79
|
function rebuildTree() {
|
|
84
|
-
if (store.
|
|
80
|
+
if (store.cellsFilteredByStatus.length === 0) return;
|
|
85
81
|
|
|
86
82
|
// Validate and clear stale localStorage
|
|
87
83
|
validateStoredConfig();
|
|
88
84
|
|
|
89
|
-
console.log('CellFilterControl: Building tree with config:', { level1, level2 });
|
|
90
|
-
|
|
91
85
|
const treeNodes = buildCellTree(
|
|
92
|
-
store.cells,
|
|
86
|
+
store.cellsFilteredByStatus, // Use filtered cells, not all cells
|
|
93
87
|
{ level1, level2 },
|
|
94
88
|
store.groupColorMap
|
|
95
89
|
);
|
|
@@ -102,18 +96,14 @@
|
|
|
102
96
|
defaultExpandAll: false
|
|
103
97
|
});
|
|
104
98
|
|
|
105
|
-
console.log('CellFilterControl: Tree store created');
|
|
106
|
-
|
|
107
99
|
// Subscribe to tree changes and update filtered cells
|
|
108
100
|
if (treeStore) {
|
|
109
101
|
const unsub = treeStore.subscribe((treeValue: TreeStoreValue) => {
|
|
110
102
|
const checkedPaths = treeValue.getCheckedPaths();
|
|
111
|
-
console.log('TreeStore updated, checked paths:', checkedPaths.length);
|
|
112
103
|
|
|
113
104
|
// Convert string[] to Set<string>
|
|
114
105
|
const checkedPathsSet = new Set(checkedPaths);
|
|
115
|
-
const newFilteredCells = getFilteredCells(checkedPathsSet, store.
|
|
116
|
-
console.log('Filtered cells count:', newFilteredCells.length, 'of', store.cells.length);
|
|
106
|
+
const newFilteredCells = getFilteredCells(checkedPathsSet, store.cellsFilteredByStatus); // Use filtered cells
|
|
117
107
|
|
|
118
108
|
// Update the cell store directly
|
|
119
109
|
store.setFilteredCells(newFilteredCells);
|
|
@@ -124,10 +114,17 @@
|
|
|
124
114
|
}
|
|
125
115
|
|
|
126
116
|
onMount(() => {
|
|
127
|
-
console.log('CellFilterControl: Mounted with', store.cells.length, 'cells');
|
|
128
117
|
rebuildTree();
|
|
129
118
|
});
|
|
130
119
|
|
|
120
|
+
// Handle includePlannedCells checkbox change
|
|
121
|
+
function handleIncludePlannedChange(event: Event) {
|
|
122
|
+
const target = event.currentTarget as HTMLInputElement;
|
|
123
|
+
store.setIncludePlannedCells(target.checked);
|
|
124
|
+
// Rebuild tree with new filtered cells
|
|
125
|
+
rebuildTree();
|
|
126
|
+
}
|
|
127
|
+
|
|
131
128
|
// Handle grouping config changes
|
|
132
129
|
function handleLevel1Change(event: Event) {
|
|
133
130
|
const target = event.currentTarget as HTMLSelectElement;
|
|
@@ -170,34 +167,57 @@
|
|
|
170
167
|
|
|
171
168
|
<MapControl {position} {title} collapsible={true} {initiallyCollapsed}>
|
|
172
169
|
<div class="cell-filter-control">
|
|
170
|
+
<!-- Status Filter Checkbox -->
|
|
171
|
+
<div class="mb-3">
|
|
172
|
+
<div class="form-check">
|
|
173
|
+
<input
|
|
174
|
+
type="checkbox"
|
|
175
|
+
class="form-check-input"
|
|
176
|
+
id="includePlanned"
|
|
177
|
+
checked={store.includePlannedCells}
|
|
178
|
+
onchange={handleIncludePlannedChange}
|
|
179
|
+
/>
|
|
180
|
+
<label class="form-check-label small" for="includePlanned">
|
|
181
|
+
Include Planned Cells
|
|
182
|
+
<span class="text-muted ms-1">
|
|
183
|
+
({store.includePlannedCells ? 'All cells' : 'On Air only'})
|
|
184
|
+
</span>
|
|
185
|
+
</label>
|
|
186
|
+
</div>
|
|
187
|
+
</div>
|
|
188
|
+
|
|
173
189
|
<!-- Grouping Configuration -->
|
|
174
190
|
<div class="grouping-config">
|
|
175
|
-
<div class="mb-
|
|
176
|
-
|
|
177
|
-
<
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
191
|
+
<div class="row g-2 mb-3">
|
|
192
|
+
<!-- Level 1 Grouping -->
|
|
193
|
+
<div class="col-6">
|
|
194
|
+
<label for="level1-select" class="form-label small mb-1">Level 1</label>
|
|
195
|
+
<select
|
|
196
|
+
id="level1-select"
|
|
197
|
+
class="form-select form-select-sm"
|
|
198
|
+
value={level1}
|
|
199
|
+
onchange={handleLevel1Change}
|
|
200
|
+
>
|
|
201
|
+
{#each GROUPING_OPTIONS as option}
|
|
202
|
+
<option value={option}>{FIELD_LABELS[option]}</option>
|
|
203
|
+
{/each}
|
|
204
|
+
</select>
|
|
205
|
+
</div>
|
|
206
|
+
|
|
207
|
+
<!-- Level 2 Grouping -->
|
|
208
|
+
<div class="col-6">
|
|
209
|
+
<label for="level2-select" class="form-label small mb-1">Level 2</label>
|
|
210
|
+
<select
|
|
211
|
+
id="level2-select"
|
|
212
|
+
class="form-select form-select-sm"
|
|
213
|
+
value={level2}
|
|
214
|
+
onchange={handleLevel2Change}
|
|
215
|
+
>
|
|
216
|
+
{#each LEVEL2_OPTIONS as option}
|
|
217
|
+
<option value={option}>{FIELD_LABELS[option]}</option>
|
|
218
|
+
{/each}
|
|
219
|
+
</select>
|
|
220
|
+
</div>
|
|
201
221
|
</div>
|
|
202
222
|
</div>
|
|
203
223
|
|
|
@@ -234,8 +254,7 @@
|
|
|
234
254
|
|
|
235
255
|
<style>
|
|
236
256
|
.cell-filter-control {
|
|
237
|
-
|
|
238
|
-
max-width: 320px;
|
|
257
|
+
width: 100%;
|
|
239
258
|
}
|
|
240
259
|
|
|
241
260
|
.grouping-config {
|
|
@@ -8,6 +8,7 @@ import type { Cell, CellStatus, CellStatusStyle, CellTreeConfig } from '../types
|
|
|
8
8
|
export interface CellStoreValue {
|
|
9
9
|
cells: Cell[];
|
|
10
10
|
filteredCells: Cell[];
|
|
11
|
+
includePlannedCells: boolean;
|
|
11
12
|
showCells: boolean;
|
|
12
13
|
lineWidth: number;
|
|
13
14
|
fillOpacity: number;
|
|
@@ -19,8 +20,10 @@ export interface CellStoreValue {
|
|
|
19
20
|
}
|
|
20
21
|
export interface CellStoreContext {
|
|
21
22
|
readonly cells: Cell[];
|
|
23
|
+
readonly cellsFilteredByStatus: Cell[];
|
|
22
24
|
readonly filteredCells: Cell[];
|
|
23
25
|
readonly showCells: boolean;
|
|
26
|
+
readonly includePlannedCells: boolean;
|
|
24
27
|
readonly lineWidth: number;
|
|
25
28
|
readonly fillOpacity: number;
|
|
26
29
|
readonly baseRadius: number;
|
|
@@ -28,6 +31,7 @@ export interface CellStoreContext {
|
|
|
28
31
|
readonly groupingConfig: CellTreeConfig;
|
|
29
32
|
readonly groupColorMap: Map<string, string>;
|
|
30
33
|
setFilteredCells(cells: Cell[]): void;
|
|
34
|
+
setIncludePlannedCells(value: boolean): void;
|
|
31
35
|
setShowCells(value: boolean): void;
|
|
32
36
|
setLineWidth(value: number): void;
|
|
33
37
|
setFillOpacity(value: number): void;
|
|
@@ -48,6 +48,7 @@ export function createCellStoreContext(cells) {
|
|
|
48
48
|
let state = $state({
|
|
49
49
|
cells,
|
|
50
50
|
filteredCells: cells,
|
|
51
|
+
includePlannedCells: persistedSettings.includePlannedCells ?? false, // Default: On Air only
|
|
51
52
|
showCells: persistedSettings.showCells ?? true,
|
|
52
53
|
lineWidth: persistedSettings.lineWidth ?? 2,
|
|
53
54
|
fillOpacity: persistedSettings.fillOpacity ?? 0.6,
|
|
@@ -57,6 +58,12 @@ export function createCellStoreContext(cells) {
|
|
|
57
58
|
groupColorMap: initialColorMap,
|
|
58
59
|
currentZoom: 12 // Default zoom
|
|
59
60
|
});
|
|
61
|
+
// Derived: Filter cells by status based on includePlannedCells flag
|
|
62
|
+
// IMPORTANT: This is a pure $derived - it only READS from state, never writes
|
|
63
|
+
let cellsFilteredByStatus = $derived(state.includePlannedCells
|
|
64
|
+
? state.cells // Include all cells
|
|
65
|
+
: state.cells.filter(cell => cell.status.startsWith('On_Air')) // Only On Air cells
|
|
66
|
+
);
|
|
60
67
|
// Auto-save settings when they change
|
|
61
68
|
$effect(() => {
|
|
62
69
|
// Convert Map to plain object for serialization
|
|
@@ -66,6 +73,7 @@ export function createCellStoreContext(cells) {
|
|
|
66
73
|
});
|
|
67
74
|
const settings = {
|
|
68
75
|
showCells: state.showCells,
|
|
76
|
+
includePlannedCells: state.includePlannedCells,
|
|
69
77
|
lineWidth: state.lineWidth,
|
|
70
78
|
fillOpacity: state.fillOpacity,
|
|
71
79
|
baseRadius: state.baseRadius,
|
|
@@ -79,8 +87,12 @@ export function createCellStoreContext(cells) {
|
|
|
79
87
|
// Bindable properties with getters/setters
|
|
80
88
|
get cells() { return state.cells; },
|
|
81
89
|
set cells(value) { state.cells = value; },
|
|
90
|
+
// Derived readonly property - filtered by status
|
|
91
|
+
get cellsFilteredByStatus() { return cellsFilteredByStatus; },
|
|
82
92
|
get filteredCells() { return state.filteredCells; },
|
|
83
93
|
set filteredCells(value) { state.filteredCells = value; },
|
|
94
|
+
get includePlannedCells() { return state.includePlannedCells; },
|
|
95
|
+
set includePlannedCells(value) { state.includePlannedCells = value; },
|
|
84
96
|
get showCells() { return state.showCells; },
|
|
85
97
|
set showCells(value) { state.showCells = value; },
|
|
86
98
|
get lineWidth() { return state.lineWidth; },
|
|
@@ -97,6 +109,9 @@ export function createCellStoreContext(cells) {
|
|
|
97
109
|
setFilteredCells(cells) {
|
|
98
110
|
state.filteredCells = cells;
|
|
99
111
|
},
|
|
112
|
+
setIncludePlannedCells(value) {
|
|
113
|
+
state.includePlannedCells = value;
|
|
114
|
+
},
|
|
100
115
|
setShowCells(value) {
|
|
101
116
|
state.showCells = value;
|
|
102
117
|
},
|
|
@@ -71,22 +71,12 @@ function generateNodeId(field, value, parentId) {
|
|
|
71
71
|
*/
|
|
72
72
|
export function buildCellTree(cells, config, colorMap) {
|
|
73
73
|
const { level1, level2 } = config;
|
|
74
|
-
console.log('=== buildCellTree START ===');
|
|
75
|
-
console.log('Config:', { level1, level2 });
|
|
76
|
-
console.log('Total cells:', cells.length);
|
|
77
74
|
// If level2 is 'none', create flat 2-level tree
|
|
78
75
|
if (level2 === 'none') {
|
|
79
|
-
|
|
80
|
-
const tree = buildFlatTree(cells, level1, colorMap);
|
|
81
|
-
console.log('Flat tree built, root children:', tree.children?.length);
|
|
82
|
-
return tree;
|
|
76
|
+
return buildFlatTree(cells, level1, colorMap);
|
|
83
77
|
}
|
|
84
78
|
// Otherwise, create nested 3-level tree
|
|
85
|
-
|
|
86
|
-
const tree = buildNestedTree(cells, level1, level2, colorMap);
|
|
87
|
-
console.log('Nested tree built, root children:', tree.children?.length);
|
|
88
|
-
console.log('=== buildCellTree END ===');
|
|
89
|
-
return tree;
|
|
79
|
+
return buildNestedTree(cells, level1, level2, colorMap);
|
|
90
80
|
}
|
|
91
81
|
/**
|
|
92
82
|
* Build flat 2-level tree (Root → Level1 groups)
|
|
@@ -162,7 +152,6 @@ function buildNestedTree(cells, level1, level2, colorMap) {
|
|
|
162
152
|
const nodeId = generateNodeId(level2, value2, parentId);
|
|
163
153
|
const color = colorMap?.get(nodeId);
|
|
164
154
|
const cellIds = groupCells.map((c) => c.id);
|
|
165
|
-
console.log(` Nested tree leaf: id="${nodeId}", cells=${cellIds.length}, cellIds sample:`, cellIds.slice(0, 3));
|
|
166
155
|
return {
|
|
167
156
|
id: nodeId,
|
|
168
157
|
label: getGroupLabel(level2, value2, groupCells.length),
|
|
@@ -181,7 +170,6 @@ function buildNestedTree(cells, level1, level2, colorMap) {
|
|
|
181
170
|
});
|
|
182
171
|
// Calculate total cells in this level1 group
|
|
183
172
|
const totalCells = Array.from(level2Groups.values()).flat().length;
|
|
184
|
-
console.log(` Nested tree parent: id="${parentId}", totalCells=${totalCells}, children=${level2Children.length}`);
|
|
185
173
|
return {
|
|
186
174
|
id: parentId,
|
|
187
175
|
label: getGroupLabel(level1, value1, totalCells),
|