@smartnet360/svelte-components 0.0.130 → 0.0.132

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 (40) hide show
  1. package/dist/core/CellTable/CellHistoryDemo.svelte +106 -65
  2. package/dist/core/CellTable/column-config.d.ts +12 -0
  3. package/dist/core/CellTable/column-config.js +79 -2
  4. package/dist/core/CellTable/history-api-helper.d.ts +79 -0
  5. package/dist/core/CellTable/history-api-helper.js +83 -0
  6. package/dist/core/CellTable/index.d.ts +3 -2
  7. package/dist/core/CellTable/index.js +3 -1
  8. package/dist/core/CellTable/types.d.ts +26 -0
  9. package/dist/map-v3/demo/DemoMap.svelte +1 -6
  10. package/dist/map-v3/features/{cells/custom → custom}/components/CustomCellFilterControl.svelte +11 -4
  11. package/dist/map-v3/features/custom/components/CustomCellSetManager.svelte +692 -0
  12. package/dist/map-v3/features/{cells/custom → custom}/index.d.ts +1 -1
  13. package/dist/map-v3/features/{cells/custom → custom}/index.js +1 -1
  14. package/dist/map-v3/features/custom/layers/CustomCellsLayer.svelte +399 -0
  15. package/dist/map-v3/features/{cells/custom → custom}/logic/csv-parser.d.ts +11 -7
  16. package/dist/map-v3/features/{cells/custom → custom}/logic/csv-parser.js +64 -20
  17. package/dist/map-v3/features/{cells/custom → custom}/logic/tree-adapter.d.ts +1 -1
  18. package/dist/map-v3/features/{cells/custom → custom}/stores/custom-cell-sets.svelte.d.ts +4 -3
  19. package/dist/map-v3/features/{cells/custom → custom}/stores/custom-cell-sets.svelte.js +30 -10
  20. package/dist/map-v3/features/{cells/custom → custom}/types.d.ts +32 -12
  21. package/dist/map-v3/features/{cells/custom → custom}/types.js +5 -3
  22. package/dist/map-v3/index.d.ts +1 -1
  23. package/dist/map-v3/index.js +1 -1
  24. package/dist/map-v3/shared/controls/MapControl.svelte +43 -15
  25. package/dist/map-v3/shared/controls/MapControl.svelte.d.ts +3 -1
  26. package/package.json +1 -1
  27. package/dist/map-v3/features/cells/custom/components/CustomCellSetManager.svelte +0 -306
  28. package/dist/map-v3/features/cells/custom/layers/CustomCellsLayer.svelte +0 -262
  29. /package/dist/map-v3/features/{cells/custom → custom}/components/CustomCellFilterControl.svelte.d.ts +0 -0
  30. /package/dist/map-v3/features/{cells/custom → custom}/components/CustomCellSetManager.svelte.d.ts +0 -0
  31. /package/dist/map-v3/features/{cells/custom → custom}/components/index.d.ts +0 -0
  32. /package/dist/map-v3/features/{cells/custom → custom}/components/index.js +0 -0
  33. /package/dist/map-v3/features/{cells/custom → custom}/layers/CustomCellsLayer.svelte.d.ts +0 -0
  34. /package/dist/map-v3/features/{cells/custom → custom}/layers/index.d.ts +0 -0
  35. /package/dist/map-v3/features/{cells/custom → custom}/layers/index.js +0 -0
  36. /package/dist/map-v3/features/{cells/custom → custom}/logic/index.d.ts +0 -0
  37. /package/dist/map-v3/features/{cells/custom → custom}/logic/index.js +0 -0
  38. /package/dist/map-v3/features/{cells/custom → custom}/logic/tree-adapter.js +0 -0
  39. /package/dist/map-v3/features/{cells/custom → custom}/stores/index.d.ts +0 -0
  40. /package/dist/map-v3/features/{cells/custom → custom}/stores/index.js +0 -0
@@ -0,0 +1,399 @@
1
+ <script lang="ts">
2
+ /**
3
+ * Custom Layer
4
+ *
5
+ * Renders custom sets on the map with sizeFactor support.
6
+ * Supports two geometry types:
7
+ * - 'cell': Sector arcs using resolved cell's azimuth/beamwidth
8
+ * - 'point': Native Mapbox circles (lightweight)
9
+ *
10
+ * Each set is rendered as separate layers for independent styling.
11
+ */
12
+ import { getContext, onMount, onDestroy } from 'svelte';
13
+ import type { MapStore } from '../../../core/stores/map.store.svelte';
14
+ import type { CustomCellSetsStore } from '../stores/custom-cell-sets.svelte';
15
+ import type { CustomCellSet, CustomCell } from '../types';
16
+ import { generateCellArc, calculateRadiusInMeters } from '../../cells/logic/geometry';
17
+ import type mapboxgl from 'mapbox-gl';
18
+
19
+ interface Props {
20
+ /** The custom cell sets store */
21
+ setsStore: CustomCellSetsStore;
22
+ /** Optional: specific set ID to render (if not provided, renders all) */
23
+ setId?: string;
24
+ }
25
+
26
+ let { setsStore, setId }: Props = $props();
27
+
28
+ const mapStore = getContext<MapStore>('MAP_CONTEXT');
29
+
30
+ // Track active layer/source IDs for cleanup
31
+ let activeSources = new Set<string>();
32
+ let activeLayers = new Set<string>();
33
+
34
+ // Debounce timer
35
+ let updateTimeout: ReturnType<typeof setTimeout>;
36
+
37
+ /**
38
+ * Get source IDs for a set (one for cells, one for points)
39
+ */
40
+ function getSourceIds(setId: string): { cells: string; points: string } {
41
+ return {
42
+ cells: `custom-cells-${setId}`,
43
+ points: `custom-points-${setId}`
44
+ };
45
+ }
46
+
47
+ /**
48
+ * Get layer IDs for a set
49
+ */
50
+ function getLayerIds(setId: string): {
51
+ cellFill: string;
52
+ cellLine: string;
53
+ pointCircle: string;
54
+ } {
55
+ return {
56
+ cellFill: `custom-cells-fill-${setId}`,
57
+ cellLine: `custom-cells-line-${setId}`,
58
+ pointCircle: `custom-points-circle-${setId}`
59
+ };
60
+ }
61
+
62
+ /**
63
+ * Add source and layers for a set
64
+ */
65
+ function addSetLayers(map: mapboxgl.Map, set: CustomCellSet) {
66
+ const sources = getSourceIds(set.id);
67
+ const layers = getLayerIds(set.id);
68
+
69
+ // Add cells source if not exists
70
+ if (!map.getSource(sources.cells)) {
71
+ map.addSource(sources.cells, {
72
+ type: 'geojson',
73
+ data: { type: 'FeatureCollection', features: [] }
74
+ });
75
+ activeSources.add(sources.cells);
76
+ }
77
+
78
+ // Add points source if not exists
79
+ if (!map.getSource(sources.points)) {
80
+ map.addSource(sources.points, {
81
+ type: 'geojson',
82
+ data: { type: 'FeatureCollection', features: [] }
83
+ });
84
+ activeSources.add(sources.points);
85
+ }
86
+
87
+ // Add cell fill layer
88
+ if (!map.getLayer(layers.cellFill)) {
89
+ map.addLayer({
90
+ id: layers.cellFill,
91
+ type: 'fill',
92
+ source: sources.cells,
93
+ paint: {
94
+ 'fill-color': ['get', 'color'],
95
+ 'fill-opacity': set.opacity
96
+ }
97
+ });
98
+ activeLayers.add(layers.cellFill);
99
+ }
100
+
101
+ // Add cell line layer
102
+ if (!map.getLayer(layers.cellLine)) {
103
+ map.addLayer({
104
+ id: layers.cellLine,
105
+ type: 'line',
106
+ source: sources.cells,
107
+ paint: {
108
+ 'line-color': ['get', 'lineColor'],
109
+ 'line-width': ['get', 'lineWidth'],
110
+ 'line-opacity': ['get', 'lineOpacity']
111
+ }
112
+ });
113
+ activeLayers.add(layers.cellLine);
114
+ }
115
+
116
+ // Add points circle layer (native Mapbox - very lightweight)
117
+ if (!map.getLayer(layers.pointCircle)) {
118
+ map.addLayer({
119
+ id: layers.pointCircle,
120
+ type: 'circle',
121
+ source: sources.points,
122
+ paint: {
123
+ 'circle-radius': ['get', 'radius'],
124
+ 'circle-color': ['get', 'color'],
125
+ 'circle-opacity': set.opacity,
126
+ 'circle-stroke-width': 1,
127
+ 'circle-stroke-color': ['get', 'strokeColor']
128
+ }
129
+ });
130
+ activeLayers.add(layers.pointCircle);
131
+ }
132
+ }
133
+
134
+ /**
135
+ * Remove layers and source for a set
136
+ */
137
+ function removeSetLayers(map: mapboxgl.Map, setIdToRemove: string) {
138
+ const sources = getSourceIds(setIdToRemove);
139
+ const layers = getLayerIds(setIdToRemove);
140
+
141
+ // Remove layers
142
+ for (const layerId of [layers.cellLine, layers.cellFill, layers.pointCircle]) {
143
+ if (map.getLayer(layerId)) {
144
+ map.removeLayer(layerId);
145
+ activeLayers.delete(layerId);
146
+ }
147
+ }
148
+
149
+ // Remove sources
150
+ for (const sourceId of [sources.cells, sources.points]) {
151
+ if (map.getSource(sourceId)) {
152
+ map.removeSource(sourceId);
153
+ activeSources.delete(sourceId);
154
+ }
155
+ }
156
+ }
157
+
158
+ /**
159
+ * Render items for a specific set
160
+ */
161
+ function renderSet(map: mapboxgl.Map, set: CustomCellSet) {
162
+ const bounds = map.getBounds();
163
+ if (!bounds) return;
164
+
165
+ const zoom = map.getZoom();
166
+ const centerLat = map.getCenter().lat;
167
+
168
+ // Calculate base radius from pixel size (for cell arcs)
169
+ const baseRadiusMeters = calculateRadiusInMeters(centerLat, zoom, set.baseSize);
170
+
171
+ const cellFeatures: GeoJSON.Feature[] = [];
172
+ const pointFeatures: GeoJSON.Feature[] = [];
173
+
174
+ for (const item of set.cells) {
175
+ // Check group visibility
176
+ if (!set.visibleGroups.has(item.customGroup)) continue;
177
+
178
+ // Get color for this group
179
+ const color = set.groupColors[item.customGroup] || set.defaultColor;
180
+
181
+ if (item.geometry === 'point' && item.lat !== undefined && item.lon !== undefined) {
182
+ // Point geometry - render as circle
183
+ // Viewport filter
184
+ if (!bounds.contains([item.lon, item.lat])) continue;
185
+
186
+ // Point radius in pixels (pointSize is in pixels)
187
+ const radius = set.pointSize * item.sizeFactor;
188
+
189
+ // Darken color for stroke
190
+ const strokeColor = darkenColor(color, 0.3);
191
+
192
+ pointFeatures.push({
193
+ type: 'Feature',
194
+ geometry: {
195
+ type: 'Point',
196
+ coordinates: [item.lon, item.lat]
197
+ },
198
+ properties: {
199
+ id: item.id,
200
+ color,
201
+ strokeColor,
202
+ radius,
203
+ customGroup: item.customGroup,
204
+ sizeFactor: item.sizeFactor,
205
+ setName: set.name,
206
+ ...Object.fromEntries(
207
+ Object.entries(item.extraFields).map(([k, v]) => [`extra_${k}`, v])
208
+ )
209
+ }
210
+ });
211
+ } else if (item.geometry === 'cell' && item.resolvedCell) {
212
+ // Cell geometry - render as sector arc
213
+ const cell = item.resolvedCell;
214
+
215
+ // Viewport filter
216
+ if (!bounds.contains([cell.longitude, cell.latitude])) continue;
217
+
218
+ // Apply size factor
219
+ const radiusMeters = baseRadiusMeters * item.sizeFactor;
220
+
221
+ // Generate arc feature
222
+ const feature = generateCellArc(cell, radiusMeters, 50, color);
223
+
224
+ // Add custom properties for tooltips
225
+ if (feature.properties) {
226
+ feature.properties.customGroup = item.customGroup;
227
+ feature.properties.sizeFactor = item.sizeFactor;
228
+ feature.properties.setName = set.name;
229
+
230
+ // Add extra fields
231
+ for (const [key, value] of Object.entries(item.extraFields)) {
232
+ feature.properties[`extra_${key}`] = value;
233
+ }
234
+ }
235
+
236
+ cellFeatures.push(feature);
237
+ }
238
+ }
239
+
240
+ // Update sources
241
+ const sources = getSourceIds(set.id);
242
+ const layers = getLayerIds(set.id);
243
+
244
+ const cellsSource = map.getSource(sources.cells) as mapboxgl.GeoJSONSource;
245
+ if (cellsSource) {
246
+ cellsSource.setData({
247
+ type: 'FeatureCollection',
248
+ features: cellFeatures as any
249
+ });
250
+ }
251
+
252
+ const pointsSource = map.getSource(sources.points) as mapboxgl.GeoJSONSource;
253
+ if (pointsSource) {
254
+ pointsSource.setData({
255
+ type: 'FeatureCollection',
256
+ features: pointFeatures
257
+ });
258
+ }
259
+
260
+ // Update layer opacity
261
+ if (map.getLayer(layers.cellFill)) {
262
+ map.setPaintProperty(layers.cellFill, 'fill-opacity', set.opacity);
263
+ }
264
+ if (map.getLayer(layers.pointCircle)) {
265
+ map.setPaintProperty(layers.pointCircle, 'circle-opacity', set.opacity);
266
+ }
267
+
268
+ console.log(`[CustomLayer] Rendered ${cellFeatures.length} cells, ${pointFeatures.length} points for set "${set.name}"`);
269
+ }
270
+
271
+ /**
272
+ * Darken a hex color by a factor
273
+ */
274
+ function darkenColor(hex: string, factor: number): string {
275
+ const num = parseInt(hex.replace('#', ''), 16);
276
+ const r = Math.floor((num >> 16) * (1 - factor));
277
+ const g = Math.floor(((num >> 8) & 0x00FF) * (1 - factor));
278
+ const b = Math.floor((num & 0x0000FF) * (1 - factor));
279
+ return `#${(r << 16 | g << 8 | b).toString(16).padStart(6, '0')}`;
280
+ }
281
+
282
+ /**
283
+ * Ensure all sources and layers exist for current sets (synchronous)
284
+ */
285
+ function ensureLayers() {
286
+ const map = mapStore.map;
287
+ if (!map) return;
288
+
289
+ const setsToRender = setId
290
+ ? setsStore.sets.filter(s => s.id === setId)
291
+ : setsStore.sets;
292
+
293
+ // Track which sets we're rendering
294
+ const activeSetIds = new Set(setsToRender.map(s => s.id));
295
+
296
+ // Remove layers for sets that no longer exist
297
+ for (const sourceId of [...activeSources]) {
298
+ const match = sourceId.match(/^custom-(?:cells|points)-(.+)$/);
299
+ if (match) {
300
+ const setIdFromSource = match[1];
301
+ if (!activeSetIds.has(setIdFromSource)) {
302
+ removeSetLayers(map, setIdFromSource);
303
+ }
304
+ }
305
+ }
306
+
307
+ // Add layers for each visible set
308
+ for (const set of setsToRender) {
309
+ if (set.visible) {
310
+ addSetLayers(map, set);
311
+ }
312
+ }
313
+ }
314
+
315
+ /**
316
+ * Update layer data (debounced)
317
+ */
318
+ function updateLayerData() {
319
+ const map = mapStore.map;
320
+ if (!map) return;
321
+
322
+ clearTimeout(updateTimeout);
323
+ updateTimeout = setTimeout(() => {
324
+ const setsToRender = setId
325
+ ? setsStore.sets.filter(s => s.id === setId)
326
+ : setsStore.sets;
327
+
328
+ // Render each set
329
+ for (const set of setsToRender) {
330
+ if (set.visible) {
331
+ renderSet(map, set);
332
+ } else {
333
+ // Hide by clearing data
334
+ const sources = getSourceIds(set.id);
335
+ const cellsSource = map.getSource(sources.cells) as mapboxgl.GeoJSONSource;
336
+ const pointsSource = map.getSource(sources.points) as mapboxgl.GeoJSONSource;
337
+ if (cellsSource) {
338
+ cellsSource.setData({ type: 'FeatureCollection', features: [] });
339
+ }
340
+ if (pointsSource) {
341
+ pointsSource.setData({ type: 'FeatureCollection', features: [] });
342
+ }
343
+ }
344
+ }
345
+ }, 100);
346
+ }
347
+
348
+ // Setup and reactive updates
349
+ $effect(() => {
350
+ const map = mapStore.map;
351
+ if (!map) return;
352
+
353
+ // When style changes, all sources/layers are removed by Mapbox
354
+ // We need to clear our tracking and re-add layers
355
+ const onStyleLoad = () => {
356
+ activeSources.clear();
357
+ activeLayers.clear();
358
+ ensureLayers();
359
+ updateLayerData();
360
+ };
361
+
362
+ // Initial setup
363
+ ensureLayers();
364
+ updateLayerData();
365
+
366
+ // Events
367
+ map.on('style.load', onStyleLoad);
368
+ map.on('moveend', updateLayerData);
369
+ map.on('zoomend', updateLayerData);
370
+
371
+ return () => {
372
+ map.off('style.load', onStyleLoad);
373
+ map.off('moveend', updateLayerData);
374
+ map.off('zoomend', updateLayerData);
375
+
376
+ // Cleanup all layers - extract unique set IDs
377
+ const setIds = new Set<string>();
378
+ for (const sourceId of activeSources) {
379
+ const match = sourceId.match(/^custom-(?:cells|points)-(.+)$/);
380
+ if (match) {
381
+ setIds.add(match[1]);
382
+ }
383
+ }
384
+ for (const setIdToRemove of setIds) {
385
+ removeSetLayers(map, setIdToRemove);
386
+ }
387
+ };
388
+ });
389
+
390
+ // React to store changes
391
+ $effect(() => {
392
+ // Read version to trigger on changes
393
+ const _version = setsStore.version;
394
+ const _sets = setsStore.sets;
395
+
396
+ ensureLayers();
397
+ updateLayerData();
398
+ });
399
+ </script>
@@ -1,23 +1,27 @@
1
1
  /**
2
- * Custom Cells - CSV Parser
2
+ * Custom Feature - CSV Parser
3
3
  *
4
- * Parses CSV files for custom cell sets.
5
- * Required column: cellName (or txId for backwards compatibility)
6
- * Optional columns: customGroup, sizeFactor, + any extras for tooltips
4
+ * Parses CSV files for custom sets.
5
+ * Supports two modes:
6
+ * - Cell mode: id column resolves against cell data → sector arcs
7
+ * - Point mode: lat/lon columns → circles
8
+ *
9
+ * Required column: id (or cellName/txId for backwards compat)
10
+ * Optional columns: customGroup, sizeFactor, lat, lon, + any extras for tooltips
7
11
  * Supports both comma (,) and semicolon (;) delimiters
8
12
  */
9
13
  import type { CustomCellImportResult } from '../types';
10
- import type { Cell } from '../../../../../shared/demo';
14
+ import type { Cell } from '../../../../shared/demo';
11
15
  /**
12
16
  * Supported delimiters
13
17
  */
14
18
  export type CsvDelimiter = ',' | ';' | 'auto';
15
19
  /**
16
- * Parse a CSV string into custom cells
20
+ * Parse a CSV string into custom items (cells or points)
17
21
  * @param csvContent Raw CSV content
18
22
  * @param cellLookup Map of cellName -> Cell for resolving cell data
19
23
  * @param delimiter Delimiter to use: ',' (comma), ';' (semicolon), or 'auto' (detect)
20
- * @returns Import result with cells, unmatched IDs, groups, and extra columns
24
+ * @returns Import result with items, unmatched IDs, groups, and extra columns
21
25
  */
22
26
  export declare function parseCustomCellsCsv(csvContent: string, cellLookup: Map<string, Cell>, delimiter?: CsvDelimiter): CustomCellImportResult;
23
27
  /**
@@ -1,15 +1,19 @@
1
1
  /**
2
- * Custom Cells - CSV Parser
2
+ * Custom Feature - CSV Parser
3
3
  *
4
- * Parses CSV files for custom cell sets.
5
- * Required column: cellName (or txId for backwards compatibility)
6
- * Optional columns: customGroup, sizeFactor, + any extras for tooltips
4
+ * Parses CSV files for custom sets.
5
+ * Supports two modes:
6
+ * - Cell mode: id column resolves against cell data → sector arcs
7
+ * - Point mode: lat/lon columns → circles
8
+ *
9
+ * Required column: id (or cellName/txId for backwards compat)
10
+ * Optional columns: customGroup, sizeFactor, lat, lon, + any extras for tooltips
7
11
  * Supports both comma (,) and semicolon (;) delimiters
8
12
  */
9
13
  /**
10
14
  * Known/reserved column names
11
15
  */
12
- const RESERVED_COLUMNS = ['cellname', 'txid', 'customgroup', 'sizefactor'];
16
+ const RESERVED_COLUMNS = ['id', 'cellname', 'txid', 'customgroup', 'sizefactor', 'lat', 'latitude', 'lon', 'lng', 'longitude'];
13
17
  /**
14
18
  * Normalize column name for matching
15
19
  */
@@ -26,11 +30,11 @@ function detectDelimiter(headerLine) {
26
30
  return semicolonCount > commaCount ? ';' : ',';
27
31
  }
28
32
  /**
29
- * Parse a CSV string into custom cells
33
+ * Parse a CSV string into custom items (cells or points)
30
34
  * @param csvContent Raw CSV content
31
35
  * @param cellLookup Map of cellName -> Cell for resolving cell data
32
36
  * @param delimiter Delimiter to use: ',' (comma), ';' (semicolon), or 'auto' (detect)
33
- * @returns Import result with cells, unmatched IDs, groups, and extra columns
37
+ * @returns Import result with items, unmatched IDs, groups, and extra columns
34
38
  */
35
39
  export function parseCustomCellsCsv(csvContent, cellLookup, delimiter = 'auto') {
36
40
  const lines = csvContent.trim().split('\n');
@@ -40,7 +44,9 @@ export function parseCustomCellsCsv(csvContent, cellLookup, delimiter = 'auto')
40
44
  unmatchedTxIds: [],
41
45
  groups: [],
42
46
  extraColumns: [],
43
- totalRows: 0
47
+ totalRows: 0,
48
+ cellCount: 0,
49
+ pointCount: 0
44
50
  };
45
51
  }
46
52
  // Detect or use specified delimiter
@@ -49,19 +55,24 @@ export function parseCustomCellsCsv(csvContent, cellLookup, delimiter = 'auto')
49
55
  // Parse header
50
56
  const headers = parseCSVLine(headerLine, actualDelimiter);
51
57
  const normalizedHeaders = headers.map(normalizeColumnName);
52
- // Find cell identifier column - prefer cellName, fallback to txId
53
- let idIndex = normalizedHeaders.findIndex(h => h === 'cellname');
54
- let usesCellName = true;
58
+ // Find ID column - prefer 'id', then 'cellname', then 'txid'
59
+ let idIndex = normalizedHeaders.findIndex(h => h === 'id');
60
+ if (idIndex === -1) {
61
+ idIndex = normalizedHeaders.findIndex(h => h === 'cellname');
62
+ }
55
63
  if (idIndex === -1) {
56
64
  idIndex = normalizedHeaders.findIndex(h => h === 'txid');
57
- usesCellName = false;
58
65
  }
59
66
  if (idIndex === -1) {
60
- throw new Error('CSV must contain a "cellName" or "txId" column');
67
+ throw new Error('CSV must contain an "id", "cellName", or "txId" column');
61
68
  }
62
69
  // Find optional columns
63
70
  const groupIndex = normalizedHeaders.findIndex(h => h === 'customgroup');
64
71
  const sizeFactorIndex = normalizedHeaders.findIndex(h => h === 'sizefactor');
72
+ // Find lat/lon columns for point geometry
73
+ let latIndex = normalizedHeaders.findIndex(h => h === 'lat' || h === 'latitude');
74
+ let lonIndex = normalizedHeaders.findIndex(h => h === 'lon' || h === 'lng' || h === 'longitude');
75
+ const hasLatLon = latIndex !== -1 && lonIndex !== -1;
65
76
  // Find extra columns (not reserved)
66
77
  const extraColumns = [];
67
78
  const extraIndices = [];
@@ -76,13 +87,15 @@ export function parseCustomCellsCsv(csvContent, cellLookup, delimiter = 'auto')
76
87
  const cells = [];
77
88
  const unmatchedTxIds = [];
78
89
  const groupsSet = new Set();
90
+ let cellCount = 0;
91
+ let pointCount = 0;
79
92
  for (let i = 1; i < lines.length; i++) {
80
93
  const line = lines[i].trim();
81
94
  if (!line)
82
95
  continue;
83
96
  const values = parseCSVLine(line, actualDelimiter);
84
- const cellIdentifier = values[idIndex]?.trim();
85
- if (!cellIdentifier)
97
+ const itemId = values[idIndex]?.trim();
98
+ if (!itemId)
86
99
  continue;
87
100
  // Get custom group (default to 'default')
88
101
  const customGroup = groupIndex !== -1
@@ -105,19 +118,48 @@ export function parseCustomCellsCsv(csvContent, cellLookup, delimiter = 'auto')
105
118
  const numValue = parseFloat(value);
106
119
  extraFields[extraColumns[i]] = isNaN(numValue) ? value : numValue;
107
120
  });
108
- // Resolve cell from lookup
109
- const resolvedCell = cellLookup.get(cellIdentifier);
121
+ // Determine geometry type and create item
122
+ let geometry = 'cell';
123
+ let resolvedCell;
124
+ let lat;
125
+ let lon;
126
+ if (hasLatLon) {
127
+ // Try to parse lat/lon for point geometry
128
+ const parsedLat = parseFloat(values[latIndex]);
129
+ const parsedLon = parseFloat(values[lonIndex]);
130
+ if (!isNaN(parsedLat) && !isNaN(parsedLon)) {
131
+ // Valid coordinates - use point geometry
132
+ geometry = 'point';
133
+ lat = parsedLat;
134
+ lon = parsedLon;
135
+ pointCount++;
136
+ cells.push({
137
+ id: itemId,
138
+ customGroup,
139
+ sizeFactor,
140
+ extraFields,
141
+ geometry,
142
+ lat,
143
+ lon
144
+ });
145
+ continue;
146
+ }
147
+ }
148
+ // No valid lat/lon - try to resolve as cell
149
+ resolvedCell = cellLookup.get(itemId);
110
150
  if (resolvedCell) {
151
+ cellCount++;
111
152
  cells.push({
112
- txId: resolvedCell.txId, // Always store the txId from resolved cell
153
+ id: itemId,
113
154
  customGroup,
114
155
  sizeFactor,
115
156
  extraFields,
157
+ geometry: 'cell',
116
158
  resolvedCell
117
159
  });
118
160
  }
119
161
  else {
120
- unmatchedTxIds.push(cellIdentifier);
162
+ unmatchedTxIds.push(itemId);
121
163
  }
122
164
  }
123
165
  return {
@@ -125,7 +167,9 @@ export function parseCustomCellsCsv(csvContent, cellLookup, delimiter = 'auto')
125
167
  unmatchedTxIds,
126
168
  groups: Array.from(groupsSet).sort(),
127
169
  extraColumns,
128
- totalRows: lines.length - 1
170
+ totalRows: lines.length - 1,
171
+ cellCount,
172
+ pointCount
129
173
  };
130
174
  }
131
175
  /**
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Builds tree structure for TreeView component, grouped by customGroup
5
5
  */
6
- import type { TreeNode } from '../../../../../core/TreeView/tree.model';
6
+ import type { TreeNode } from '../../../../core/TreeView/tree.model';
7
7
  import type { CustomCellSet } from '../types';
8
8
  /**
9
9
  * Metadata for tree nodes
@@ -4,8 +4,8 @@
4
4
  * Manages multiple custom cell sets, each loaded from a CSV file.
5
5
  * Resolves cell data from a provided cell array.
6
6
  */
7
- import type { Cell } from '../../types';
8
- import type { CellDataStore } from '../../stores/cell.data.svelte';
7
+ import type { Cell } from '../../../../shared/demo';
8
+ import type { CellDataStore } from '../../cells/stores/cell.data.svelte';
9
9
  import type { CustomCellSet, CustomCellImportResult } from '../types';
10
10
  /** Function that returns the current cells array */
11
11
  type CellsGetter = () => Cell[];
@@ -58,7 +58,7 @@ export declare class CustomCellSetsStore {
58
58
  /**
59
59
  * Update set display settings
60
60
  */
61
- updateSetSettings(setId: string, settings: Partial<Pick<CustomCellSet, 'baseSize' | 'opacity' | 'defaultColor'>>): void;
61
+ updateSetSettings(setId: string, settings: Partial<Pick<CustomCellSet, 'baseSize' | 'pointSize' | 'opacity' | 'defaultColor'>>): void;
62
62
  /**
63
63
  * Rename a set
64
64
  */
@@ -69,6 +69,7 @@ export declare class CustomCellSetsStore {
69
69
  getVisibleCells(setId: string): import("..").CustomCell[];
70
70
  /**
71
71
  * Re-resolve cells after main cell data changes
72
+ * Only affects items with 'cell' geometry
72
73
  */
73
74
  refreshResolutions(): void;
74
75
  /**