@smartnet360/svelte-components 0.0.77 → 0.0.79
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 +18 -0
- package/dist/map-v2/index.d.ts +1 -1
- package/dist/map-v2/index.js +1 -1
- package/dist/map-v2/shared/controls/FeatureSelectionControl.svelte +342 -0
- package/dist/map-v2/shared/controls/FeatureSelectionControl.svelte.d.ts +19 -0
- package/dist/map-v2/shared/controls/featureSelectionStore.svelte.d.ts +81 -0
- package/dist/map-v2/shared/controls/featureSelectionStore.svelte.js +156 -0
- package/dist/map-v2/shared/index.d.ts +3 -0
- package/dist/map-v2/shared/index.js +3 -0
- package/package.json +1 -1
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
import RepeaterLabelsLayer from '../features/repeaters/layers/RepeaterLabelsLayer.svelte';
|
|
28
28
|
import RepeaterFilterControl from '../features/repeaters/controls/RepeaterFilterControl.svelte';
|
|
29
29
|
import FeatureSettingsControl from '../shared/controls/FeatureSettingsControl.svelte';
|
|
30
|
+
import FeatureSelectionControl from '../shared/controls/FeatureSelectionControl.svelte';
|
|
30
31
|
import { createSiteStoreContext } from '../features/sites/stores/siteStoreContext.svelte';
|
|
31
32
|
import { createCellStoreContext } from '../features/cells/stores/cellStoreContext.svelte';
|
|
32
33
|
import { createRepeaterStoreContext } from '../features/repeaters/stores/repeaterStoreContext.svelte';
|
|
@@ -87,6 +88,12 @@
|
|
|
87
88
|
alert(`Selected ${siteIds.length} sites:\n${siteIds.join(', ')}`);
|
|
88
89
|
}
|
|
89
90
|
|
|
91
|
+
// Handler for generic feature selection action button
|
|
92
|
+
function handleProcessFeatures(featureIds: string[]) {
|
|
93
|
+
console.log('Process features:', featureIds);
|
|
94
|
+
alert(`Selected ${featureIds.length} features:\n${featureIds.join(', ')}`);
|
|
95
|
+
}
|
|
96
|
+
|
|
90
97
|
// Cell filter grouping configuration
|
|
91
98
|
const cellLevel1Options: Array<Exclude<CellGroupingField, 'none'>> = [
|
|
92
99
|
'tech',
|
|
@@ -222,6 +229,17 @@
|
|
|
222
229
|
actionButtonLabel="Open Cluster KPIs"
|
|
223
230
|
/>
|
|
224
231
|
|
|
232
|
+
<!-- Generic feature selection control - works with any layer -->
|
|
233
|
+
<FeatureSelectionControl
|
|
234
|
+
position="bottom-left"
|
|
235
|
+
title="Any Feature"
|
|
236
|
+
icon="cursor-fill"
|
|
237
|
+
iconOnlyWhenCollapsed={useIconHeaders}
|
|
238
|
+
onAction={handleProcessFeatures}
|
|
239
|
+
actionButtonLabel="Process Features"
|
|
240
|
+
featureIcon="pin-map-fill"
|
|
241
|
+
/>
|
|
242
|
+
|
|
225
243
|
<!-- Unified feature settings control - Sites, Cells, and Repeaters -->
|
|
226
244
|
<FeatureSettingsControl
|
|
227
245
|
siteStore={siteStore}
|
package/dist/map-v2/index.d.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Each feature (sites, cells) is completely independent with its own store, layers, and controls.
|
|
6
6
|
*/
|
|
7
7
|
export { type MapStore, MAP_CONTEXT_KEY, MapboxProvider, ViewportSync, MapStoreBridge, MapStyleControl, createMapStore, createViewportStore, type ViewportStore, type ViewportState, useMapbox, tryUseMapbox } from './core';
|
|
8
|
-
export { MapControl, FeatureSettingsControl, addSourceIfMissing, removeSourceIfExists, addLayerIfMissing, removeLayerIfExists, updateGeoJSONSource, removeLayerAndSource, isStyleLoaded, waitForStyleLoad, setFeatureState, removeFeatureState, generateLayerId, generateSourceId } from './shared';
|
|
8
|
+
export { MapControl, FeatureSettingsControl, FeatureSelectionControl, createFeatureSelectionStore, FeatureSelectionStore, type SelectedFeature, 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
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';
|
package/dist/map-v2/index.js
CHANGED
|
@@ -11,7 +11,7 @@ export { MAP_CONTEXT_KEY, MapboxProvider, ViewportSync, MapStoreBridge, MapStyle
|
|
|
11
11
|
// ============================================================================
|
|
12
12
|
// SHARED UTILITIES
|
|
13
13
|
// ============================================================================
|
|
14
|
-
export { MapControl, FeatureSettingsControl, addSourceIfMissing, removeSourceIfExists, addLayerIfMissing, removeLayerIfExists, updateGeoJSONSource, removeLayerAndSource, isStyleLoaded, waitForStyleLoad, setFeatureState, removeFeatureState, generateLayerId, generateSourceId } from './shared';
|
|
14
|
+
export { MapControl, FeatureSettingsControl, FeatureSelectionControl, createFeatureSelectionStore, FeatureSelectionStore, addSourceIfMissing, removeSourceIfExists, addLayerIfMissing, removeLayerIfExists, updateGeoJSONSource, removeLayerAndSource, isStyleLoaded, waitForStyleLoad, setFeatureState, removeFeatureState, generateLayerId, generateSourceId } from './shared';
|
|
15
15
|
// ============================================================================
|
|
16
16
|
// SITE FEATURE
|
|
17
17
|
// ============================================================================
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* FeatureSelectionControl - Layer-agnostic feature selection control
|
|
4
|
+
*
|
|
5
|
+
* Features:
|
|
6
|
+
* - Works with ANY map layer that has features with 'id' property
|
|
7
|
+
* - Toggle to enable/disable click-to-select mode
|
|
8
|
+
* - List of selected feature IDs
|
|
9
|
+
* - Individual delete and clear all
|
|
10
|
+
* - Copy feature IDs to clipboard
|
|
11
|
+
* - Custom action button with callback
|
|
12
|
+
* - Self-contained with its own store
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { onMount, onDestroy, getContext } from 'svelte';
|
|
16
|
+
import { get } from 'svelte/store';
|
|
17
|
+
import MapControl from './MapControl.svelte';
|
|
18
|
+
import { createFeatureSelectionStore, type SelectedFeature } from './featureSelectionStore.svelte';
|
|
19
|
+
import { MAP_CONTEXT_KEY, type MapStore } from '../../core/types';
|
|
20
|
+
|
|
21
|
+
interface Props {
|
|
22
|
+
/** Control position */
|
|
23
|
+
position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
|
|
24
|
+
/** Control title */
|
|
25
|
+
title?: string;
|
|
26
|
+
/** Optional header icon */
|
|
27
|
+
icon?: string;
|
|
28
|
+
/** Show icon when collapsed (default: true) */
|
|
29
|
+
iconOnlyWhenCollapsed?: boolean;
|
|
30
|
+
/** Callback when action button clicked */
|
|
31
|
+
onAction?: (featureIds: string[]) => void;
|
|
32
|
+
/** Action button label */
|
|
33
|
+
actionButtonLabel?: string;
|
|
34
|
+
/** Feature icon (default: geo-alt-fill) */
|
|
35
|
+
featureIcon?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
let {
|
|
39
|
+
position = 'top-left',
|
|
40
|
+
title = 'Feature Selection',
|
|
41
|
+
icon = 'check2-square',
|
|
42
|
+
iconOnlyWhenCollapsed = true,
|
|
43
|
+
onAction,
|
|
44
|
+
actionButtonLabel = 'Process',
|
|
45
|
+
featureIcon = 'geo-alt-fill'
|
|
46
|
+
}: Props = $props();
|
|
47
|
+
|
|
48
|
+
// Get map from context
|
|
49
|
+
const mapStore = getContext<MapStore>(MAP_CONTEXT_KEY);
|
|
50
|
+
console.log('[FeatureSelectionControl] mapStore from context:', mapStore);
|
|
51
|
+
|
|
52
|
+
// Create self-contained store
|
|
53
|
+
const store = createFeatureSelectionStore();
|
|
54
|
+
console.log('[FeatureSelectionControl] Store created:', store);
|
|
55
|
+
|
|
56
|
+
let selectedFeatures = $derived(store.getSelectedFeatures());
|
|
57
|
+
let selectionCount = $derived(store.count);
|
|
58
|
+
let hasSelection = $derived(selectionCount > 0);
|
|
59
|
+
|
|
60
|
+
// Subscribe to map store and initialize when map becomes available
|
|
61
|
+
let unsubscribe: (() => void) | null = null;
|
|
62
|
+
|
|
63
|
+
onMount(() => {
|
|
64
|
+
console.log('[FeatureSelectionControl] Component mounted');
|
|
65
|
+
|
|
66
|
+
// Subscribe to map store to get notified when map is ready
|
|
67
|
+
unsubscribe = mapStore.subscribe((map) => {
|
|
68
|
+
console.log('[FeatureSelectionControl] Map store updated:', map);
|
|
69
|
+
if (map && !store['map']) {
|
|
70
|
+
console.log('[FeatureSelectionControl] Setting map on selection store');
|
|
71
|
+
store.setMap(map);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
onDestroy(() => {
|
|
77
|
+
// Unsubscribe from map store
|
|
78
|
+
if (unsubscribe) {
|
|
79
|
+
unsubscribe();
|
|
80
|
+
}
|
|
81
|
+
// Cleanup event handlers
|
|
82
|
+
store.destroy();
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
function handleToggleMode() {
|
|
86
|
+
store.toggleSelectionMode();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function handleRemoveFeature(featureId: string) {
|
|
90
|
+
store.removeFeatureSelection(featureId);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function handleClearAll() {
|
|
94
|
+
store.clearSelection();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async function handleCopy() {
|
|
98
|
+
const ids = store.getSelectedIds().join(',');
|
|
99
|
+
try {
|
|
100
|
+
await navigator.clipboard.writeText(ids);
|
|
101
|
+
// Optional: Show toast notification
|
|
102
|
+
} catch (err) {
|
|
103
|
+
console.error('Failed to copy:', err);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function handleAction() {
|
|
108
|
+
if (onAction && hasSelection) {
|
|
109
|
+
const ids = store.getSelectedIds();
|
|
110
|
+
onAction(ids);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
</script>
|
|
114
|
+
|
|
115
|
+
<MapControl {position} {title} {icon} {iconOnlyWhenCollapsed} collapsible={true}>
|
|
116
|
+
<div class="feature-selection-control">
|
|
117
|
+
<!-- Selection Mode Toggle -->
|
|
118
|
+
<div class="selection-mode-toggle mb-3">
|
|
119
|
+
<div class="form-check form-switch">
|
|
120
|
+
<input
|
|
121
|
+
type="checkbox"
|
|
122
|
+
class="form-check-input"
|
|
123
|
+
id="feature-selection-mode-toggle"
|
|
124
|
+
checked={store.selectionMode}
|
|
125
|
+
onchange={handleToggleMode}
|
|
126
|
+
/>
|
|
127
|
+
<label class="form-check-label" for="feature-selection-mode-toggle">
|
|
128
|
+
Selection Mode
|
|
129
|
+
<span class="badge ms-2" class:bg-success={store.selectionMode} class:bg-secondary={!store.selectionMode}>
|
|
130
|
+
{store.selectionMode ? 'ON' : 'OFF'}
|
|
131
|
+
</span>
|
|
132
|
+
</label>
|
|
133
|
+
</div>
|
|
134
|
+
{#if store.selectionMode}
|
|
135
|
+
<small class="text-muted d-block mt-1">
|
|
136
|
+
Click any feature on the map to select
|
|
137
|
+
</small>
|
|
138
|
+
{/if}
|
|
139
|
+
</div>
|
|
140
|
+
|
|
141
|
+
<!-- Selection Stats -->
|
|
142
|
+
<div class="selection-stats mb-2">
|
|
143
|
+
<strong>{selectionCount}</strong>
|
|
144
|
+
{selectionCount === 1 ? 'feature' : 'features'} selected
|
|
145
|
+
</div>
|
|
146
|
+
|
|
147
|
+
<!-- Action Buttons -->
|
|
148
|
+
{#if hasSelection}
|
|
149
|
+
<div class="action-buttons mb-3">
|
|
150
|
+
<div class="btn-group w-100" role="group">
|
|
151
|
+
<button
|
|
152
|
+
type="button"
|
|
153
|
+
class="btn btn-sm btn-outline-danger"
|
|
154
|
+
onclick={handleClearAll}
|
|
155
|
+
title="Clear all"
|
|
156
|
+
>
|
|
157
|
+
<i class="bi bi-trash"></i> Clear
|
|
158
|
+
</button>
|
|
159
|
+
<button
|
|
160
|
+
type="button"
|
|
161
|
+
class="btn btn-sm btn-outline-secondary"
|
|
162
|
+
onclick={handleCopy}
|
|
163
|
+
title="Copy feature IDs"
|
|
164
|
+
>
|
|
165
|
+
<i class="bi bi-clipboard"></i> Copy
|
|
166
|
+
</button>
|
|
167
|
+
</div>
|
|
168
|
+
</div>
|
|
169
|
+
{/if}
|
|
170
|
+
|
|
171
|
+
<!-- Feature List -->
|
|
172
|
+
{#if hasSelection}
|
|
173
|
+
<div class="feature-list">
|
|
174
|
+
{#each selectedFeatures as feature (feature.id)}
|
|
175
|
+
<div class="feature-item">
|
|
176
|
+
<i class="bi bi-{featureIcon} feature-icon"></i>
|
|
177
|
+
<div class="feature-info">
|
|
178
|
+
<span class="feature-id">{feature.id}</span>
|
|
179
|
+
{#if feature.layerId}
|
|
180
|
+
<small class="feature-layer text-muted">{feature.layerId}</small>
|
|
181
|
+
{/if}
|
|
182
|
+
</div>
|
|
183
|
+
<button
|
|
184
|
+
type="button"
|
|
185
|
+
class="btn-remove"
|
|
186
|
+
onclick={() => handleRemoveFeature(feature.id)}
|
|
187
|
+
title="Remove"
|
|
188
|
+
aria-label="Remove {feature.id}"
|
|
189
|
+
>
|
|
190
|
+
<i class="bi bi-x"></i>
|
|
191
|
+
</button>
|
|
192
|
+
</div>
|
|
193
|
+
{/each}
|
|
194
|
+
</div>
|
|
195
|
+
{:else}
|
|
196
|
+
<div class="text-muted small text-center py-2">
|
|
197
|
+
<i class="bi bi-inbox"></i>
|
|
198
|
+
<div class="mt-1">No features selected</div>
|
|
199
|
+
</div>
|
|
200
|
+
{/if}
|
|
201
|
+
|
|
202
|
+
<!-- Action Button -->
|
|
203
|
+
{#if onAction}
|
|
204
|
+
<div class="mt-3">
|
|
205
|
+
<button
|
|
206
|
+
type="button"
|
|
207
|
+
class="btn btn-primary w-100"
|
|
208
|
+
disabled={!hasSelection}
|
|
209
|
+
onclick={handleAction}
|
|
210
|
+
>
|
|
211
|
+
<i class="bi bi-lightning-charge-fill"></i> {actionButtonLabel}
|
|
212
|
+
</button>
|
|
213
|
+
</div>
|
|
214
|
+
{/if}
|
|
215
|
+
</div>
|
|
216
|
+
</MapControl>
|
|
217
|
+
|
|
218
|
+
<style>
|
|
219
|
+
.feature-selection-control {
|
|
220
|
+
width: 100%;
|
|
221
|
+
min-width: 250px;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
.selection-mode-toggle {
|
|
225
|
+
padding-bottom: 0.75rem;
|
|
226
|
+
border-bottom: 1px solid #dee2e6;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
.selection-stats {
|
|
230
|
+
font-size: 0.875rem;
|
|
231
|
+
color: #495057;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
.action-buttons {
|
|
235
|
+
display: flex;
|
|
236
|
+
gap: 0.5rem;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
.feature-list {
|
|
240
|
+
max-height: 300px;
|
|
241
|
+
overflow-y: auto;
|
|
242
|
+
border: 1px solid #dee2e6;
|
|
243
|
+
border-radius: 4px;
|
|
244
|
+
padding: 0.5rem;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
.feature-item {
|
|
248
|
+
display: flex;
|
|
249
|
+
align-items: center;
|
|
250
|
+
gap: 0.5rem;
|
|
251
|
+
padding: 0.5rem;
|
|
252
|
+
border-bottom: 1px solid #f1f3f5;
|
|
253
|
+
transition: background-color 0.15s;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
.feature-item:last-child {
|
|
257
|
+
border-bottom: none;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
.feature-item:hover {
|
|
261
|
+
background-color: #f8f9fa;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
.feature-icon {
|
|
265
|
+
font-size: 1rem;
|
|
266
|
+
flex-shrink: 0;
|
|
267
|
+
color: #0d6efd;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
.feature-info {
|
|
271
|
+
flex: 1;
|
|
272
|
+
display: flex;
|
|
273
|
+
flex-direction: column;
|
|
274
|
+
gap: 0.125rem;
|
|
275
|
+
min-width: 0;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
.feature-id {
|
|
279
|
+
font-family: 'Monaco', 'Courier New', monospace;
|
|
280
|
+
font-size: 0.875rem;
|
|
281
|
+
color: #212529;
|
|
282
|
+
white-space: nowrap;
|
|
283
|
+
overflow: hidden;
|
|
284
|
+
text-overflow: ellipsis;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
.feature-layer {
|
|
288
|
+
font-size: 0.75rem;
|
|
289
|
+
white-space: nowrap;
|
|
290
|
+
overflow: hidden;
|
|
291
|
+
text-overflow: ellipsis;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
.btn-remove {
|
|
295
|
+
background: none;
|
|
296
|
+
border: none;
|
|
297
|
+
color: #6c757d;
|
|
298
|
+
font-size: 1.25rem;
|
|
299
|
+
line-height: 1;
|
|
300
|
+
padding: 0;
|
|
301
|
+
width: 24px;
|
|
302
|
+
height: 24px;
|
|
303
|
+
display: flex;
|
|
304
|
+
align-items: center;
|
|
305
|
+
justify-content: center;
|
|
306
|
+
cursor: pointer;
|
|
307
|
+
border-radius: 4px;
|
|
308
|
+
transition: all 0.15s;
|
|
309
|
+
flex-shrink: 0;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
.btn-remove:hover {
|
|
313
|
+
background-color: #ffe6e6;
|
|
314
|
+
color: #dc3545;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
.btn-remove:active {
|
|
318
|
+
background-color: #ffcccc;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/* Ensure primary action button keeps Bootstrap styling inside Mapbox control */
|
|
322
|
+
.feature-selection-control .btn-primary {
|
|
323
|
+
background-color: var(--bs-btn-bg, var(--bs-primary));
|
|
324
|
+
border-color: var(--bs-btn-border-color, var(--bs-primary));
|
|
325
|
+
color: var(--bs-btn-color, var(--bs-body-color));
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
.feature-selection-control .btn-primary:hover,
|
|
329
|
+
.feature-selection-control .btn-primary:focus {
|
|
330
|
+
background-color: var(--bs-btn-hover-bg, var(--bs-primary));
|
|
331
|
+
border-color: var(--bs-btn-hover-border-color, var(--bs-primary));
|
|
332
|
+
color: var(--bs-btn-hover-color, var(--bs-btn-color, var(--bs-body-color)));
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
.feature-selection-control .btn-primary:disabled,
|
|
336
|
+
.feature-selection-control .btn-primary:disabled:hover {
|
|
337
|
+
background-color: var(--bs-btn-disabled-bg, var(--bs-btn-bg, var(--bs-primary)));
|
|
338
|
+
border-color: var(--bs-btn-disabled-border-color, var(--bs-btn-border-color, var(--bs-primary)));
|
|
339
|
+
color: var(--bs-btn-disabled-color, var(--bs-btn-color, var(--bs-body-color)));
|
|
340
|
+
opacity: var(--bs-btn-disabled-opacity, 0.65);
|
|
341
|
+
}
|
|
342
|
+
</style>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
/** Control position */
|
|
3
|
+
position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
|
|
4
|
+
/** Control title */
|
|
5
|
+
title?: string;
|
|
6
|
+
/** Optional header icon */
|
|
7
|
+
icon?: string;
|
|
8
|
+
/** Show icon when collapsed (default: true) */
|
|
9
|
+
iconOnlyWhenCollapsed?: boolean;
|
|
10
|
+
/** Callback when action button clicked */
|
|
11
|
+
onAction?: (featureIds: string[]) => void;
|
|
12
|
+
/** Action button label */
|
|
13
|
+
actionButtonLabel?: string;
|
|
14
|
+
/** Feature icon (default: geo-alt-fill) */
|
|
15
|
+
featureIcon?: string;
|
|
16
|
+
}
|
|
17
|
+
declare const FeatureSelectionControl: import("svelte").Component<Props, {}, "">;
|
|
18
|
+
type FeatureSelectionControl = ReturnType<typeof FeatureSelectionControl>;
|
|
19
|
+
export default FeatureSelectionControl;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Feature Selection Store - Generic layer-agnostic feature selection
|
|
3
|
+
*
|
|
4
|
+
* Manages selection of any map features that have an 'id' property.
|
|
5
|
+
* Self-contained store that can be used independently of specific layers.
|
|
6
|
+
*/
|
|
7
|
+
import type { Map as MapboxMap } from 'mapbox-gl';
|
|
8
|
+
export interface SelectedFeature {
|
|
9
|
+
id: string;
|
|
10
|
+
siteId?: string;
|
|
11
|
+
cellName?: string;
|
|
12
|
+
layerId?: string;
|
|
13
|
+
properties?: Record<string, any>;
|
|
14
|
+
}
|
|
15
|
+
export declare class FeatureSelectionStore {
|
|
16
|
+
private selectedFeatures;
|
|
17
|
+
private map;
|
|
18
|
+
selectionMode: boolean;
|
|
19
|
+
private clickHandler;
|
|
20
|
+
constructor();
|
|
21
|
+
/**
|
|
22
|
+
* Initialize the store with a map instance
|
|
23
|
+
*/
|
|
24
|
+
setMap(mapInstance: MapboxMap): void;
|
|
25
|
+
/**
|
|
26
|
+
* Setup global click handler for any feature
|
|
27
|
+
*/
|
|
28
|
+
private setupClickHandler;
|
|
29
|
+
/**
|
|
30
|
+
* Toggle selection mode on/off
|
|
31
|
+
*/
|
|
32
|
+
toggleSelectionMode(): void;
|
|
33
|
+
/**
|
|
34
|
+
* Enable selection mode
|
|
35
|
+
*/
|
|
36
|
+
enableSelectionMode(): void;
|
|
37
|
+
/**
|
|
38
|
+
* Disable selection mode
|
|
39
|
+
*/
|
|
40
|
+
disableSelectionMode(): void;
|
|
41
|
+
/**
|
|
42
|
+
* Toggle a feature in the selection
|
|
43
|
+
*/
|
|
44
|
+
toggleFeatureSelection(id: string, layerId?: string, properties?: Record<string, any>, siteId?: string, cellName?: string): void;
|
|
45
|
+
/**
|
|
46
|
+
* Add a feature to the selection
|
|
47
|
+
*/
|
|
48
|
+
addFeatureSelection(id: string, layerId?: string, properties?: Record<string, any>, siteId?: string, cellName?: string): void;
|
|
49
|
+
/**
|
|
50
|
+
* Remove a feature from the selection
|
|
51
|
+
*/
|
|
52
|
+
removeFeatureSelection(id: string): void;
|
|
53
|
+
/**
|
|
54
|
+
* Clear all selections
|
|
55
|
+
*/
|
|
56
|
+
clearSelection(): void;
|
|
57
|
+
/**
|
|
58
|
+
* Get all selected features
|
|
59
|
+
*/
|
|
60
|
+
getSelectedFeatures(): SelectedFeature[];
|
|
61
|
+
/**
|
|
62
|
+
* Get selected feature IDs only
|
|
63
|
+
*/
|
|
64
|
+
getSelectedIds(): string[];
|
|
65
|
+
/**
|
|
66
|
+
* Check if a feature is selected
|
|
67
|
+
*/
|
|
68
|
+
isFeatureSelected(id: string): boolean;
|
|
69
|
+
/**
|
|
70
|
+
* Get selection count
|
|
71
|
+
*/
|
|
72
|
+
get count(): number;
|
|
73
|
+
/**
|
|
74
|
+
* Cleanup - remove event handlers
|
|
75
|
+
*/
|
|
76
|
+
destroy(): void;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Factory function to create a new feature selection store
|
|
80
|
+
*/
|
|
81
|
+
export declare function createFeatureSelectionStore(): FeatureSelectionStore;
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Feature Selection Store - Generic layer-agnostic feature selection
|
|
3
|
+
*
|
|
4
|
+
* Manages selection of any map features that have an 'id' property.
|
|
5
|
+
* Self-contained store that can be used independently of specific layers.
|
|
6
|
+
*/
|
|
7
|
+
export class FeatureSelectionStore {
|
|
8
|
+
selectedFeatures = $state([]);
|
|
9
|
+
map = $state(null);
|
|
10
|
+
selectionMode = $state(false);
|
|
11
|
+
clickHandler = null;
|
|
12
|
+
constructor() { }
|
|
13
|
+
/**
|
|
14
|
+
* Initialize the store with a map instance
|
|
15
|
+
*/
|
|
16
|
+
setMap(mapInstance) {
|
|
17
|
+
console.log('[FeatureSelection] setMap called with:', mapInstance);
|
|
18
|
+
this.map = mapInstance;
|
|
19
|
+
this.setupClickHandler();
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Setup global click handler for any feature
|
|
23
|
+
*/
|
|
24
|
+
setupClickHandler() {
|
|
25
|
+
if (!this.map)
|
|
26
|
+
return;
|
|
27
|
+
this.clickHandler = (e) => {
|
|
28
|
+
console.log('[FeatureSelection] Map clicked, selectionMode:', this.selectionMode);
|
|
29
|
+
if (!this.selectionMode)
|
|
30
|
+
return;
|
|
31
|
+
// Query all rendered features at the click point
|
|
32
|
+
const features = this.map.queryRenderedFeatures(e.point);
|
|
33
|
+
console.log('[FeatureSelection] Features found:', features?.length || 0);
|
|
34
|
+
if (features && features.length > 0) {
|
|
35
|
+
// Get the topmost feature with an id
|
|
36
|
+
for (const feature of features) {
|
|
37
|
+
console.log('[FeatureSelection] Feature:', {
|
|
38
|
+
layer: feature.layer?.id,
|
|
39
|
+
properties: feature.properties,
|
|
40
|
+
id: feature.id
|
|
41
|
+
});
|
|
42
|
+
const featureId = feature.properties?.id || feature.id;
|
|
43
|
+
const siteId = feature.properties?.siteId;
|
|
44
|
+
const cellName = feature.properties?.cellName;
|
|
45
|
+
if (featureId) {
|
|
46
|
+
console.log('[FeatureSelection] Found feature with ID:', featureId, 'siteId:', siteId, 'cellName:', cellName);
|
|
47
|
+
this.toggleFeatureSelection(String(featureId), feature.layer?.id, feature.properties || undefined, siteId, cellName);
|
|
48
|
+
break; // Only select the topmost feature
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
console.log('[FeatureSelection] Feature has no id property');
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
console.log('[FeatureSelection] Click handler registered on map');
|
|
57
|
+
this.map.on('click', this.clickHandler);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Toggle selection mode on/off
|
|
61
|
+
*/
|
|
62
|
+
toggleSelectionMode() {
|
|
63
|
+
this.selectionMode = !this.selectionMode;
|
|
64
|
+
console.log('[FeatureSelection] Selection mode toggled to:', this.selectionMode);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Enable selection mode
|
|
68
|
+
*/
|
|
69
|
+
enableSelectionMode() {
|
|
70
|
+
this.selectionMode = true;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Disable selection mode
|
|
74
|
+
*/
|
|
75
|
+
disableSelectionMode() {
|
|
76
|
+
this.selectionMode = false;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Toggle a feature in the selection
|
|
80
|
+
*/
|
|
81
|
+
toggleFeatureSelection(id, layerId, properties, siteId, cellName) {
|
|
82
|
+
const index = this.selectedFeatures.findIndex(f => f.id === id);
|
|
83
|
+
if (index >= 0) {
|
|
84
|
+
// Remove if already selected
|
|
85
|
+
this.selectedFeatures.splice(index, 1);
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
// Add if not selected
|
|
89
|
+
this.selectedFeatures.push({ id, layerId, properties, siteId, cellName });
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Add a feature to the selection
|
|
94
|
+
*/
|
|
95
|
+
addFeatureSelection(id, layerId, properties, siteId, cellName) {
|
|
96
|
+
const exists = this.selectedFeatures.some(f => f.id === id);
|
|
97
|
+
if (!exists) {
|
|
98
|
+
this.selectedFeatures.push({ id, layerId, properties, siteId, cellName });
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Remove a feature from the selection
|
|
103
|
+
*/
|
|
104
|
+
removeFeatureSelection(id) {
|
|
105
|
+
const index = this.selectedFeatures.findIndex(f => f.id === id);
|
|
106
|
+
if (index >= 0) {
|
|
107
|
+
this.selectedFeatures.splice(index, 1);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Clear all selections
|
|
112
|
+
*/
|
|
113
|
+
clearSelection() {
|
|
114
|
+
this.selectedFeatures = [];
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Get all selected features
|
|
118
|
+
*/
|
|
119
|
+
getSelectedFeatures() {
|
|
120
|
+
return this.selectedFeatures;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Get selected feature IDs only
|
|
124
|
+
*/
|
|
125
|
+
getSelectedIds() {
|
|
126
|
+
return this.selectedFeatures.map(f => f.id);
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Check if a feature is selected
|
|
130
|
+
*/
|
|
131
|
+
isFeatureSelected(id) {
|
|
132
|
+
return this.selectedFeatures.some(f => f.id === id);
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Get selection count
|
|
136
|
+
*/
|
|
137
|
+
get count() {
|
|
138
|
+
return this.selectedFeatures.length;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Cleanup - remove event handlers
|
|
142
|
+
*/
|
|
143
|
+
destroy() {
|
|
144
|
+
if (this.map && this.clickHandler) {
|
|
145
|
+
this.map.off('click', this.clickHandler);
|
|
146
|
+
}
|
|
147
|
+
this.clearSelection();
|
|
148
|
+
this.map = null;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Factory function to create a new feature selection store
|
|
153
|
+
*/
|
|
154
|
+
export function createFeatureSelectionStore() {
|
|
155
|
+
return new FeatureSelectionStore();
|
|
156
|
+
}
|
|
@@ -5,4 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
export { default as MapControl } from './controls/MapControl.svelte';
|
|
7
7
|
export { default as FeatureSettingsControl } from './controls/FeatureSettingsControl.svelte';
|
|
8
|
+
export { default as FeatureSelectionControl } from './controls/FeatureSelectionControl.svelte';
|
|
9
|
+
export { createFeatureSelectionStore, FeatureSelectionStore } from './controls/featureSelectionStore.svelte';
|
|
10
|
+
export type { SelectedFeature } from './controls/featureSelectionStore.svelte';
|
|
8
11
|
export { addSourceIfMissing, removeSourceIfExists, addLayerIfMissing, removeLayerIfExists, updateGeoJSONSource, removeLayerAndSource, isStyleLoaded, waitForStyleLoad, setFeatureState, removeFeatureState, generateLayerId, generateSourceId } from './utils/mapboxHelpers';
|
|
@@ -6,5 +6,8 @@
|
|
|
6
6
|
// Controls
|
|
7
7
|
export { default as MapControl } from './controls/MapControl.svelte';
|
|
8
8
|
export { default as FeatureSettingsControl } from './controls/FeatureSettingsControl.svelte';
|
|
9
|
+
export { default as FeatureSelectionControl } from './controls/FeatureSelectionControl.svelte';
|
|
10
|
+
// Feature Selection Store
|
|
11
|
+
export { createFeatureSelectionStore, FeatureSelectionStore } from './controls/featureSelectionStore.svelte';
|
|
9
12
|
// Mapbox Helpers
|
|
10
13
|
export { addSourceIfMissing, removeSourceIfExists, addLayerIfMissing, removeLayerIfExists, updateGeoJSONSource, removeLayerAndSource, isStyleLoaded, waitForStyleLoad, setFeatureState, removeFeatureState, generateLayerId, generateSourceId } from './utils/mapboxHelpers';
|