@smartnet360/svelte-components 0.0.145 → 0.0.147

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.
@@ -494,6 +494,7 @@
494
494
  {#each setsStore.sets as set (set.id)}
495
495
  {@const isExpanded = expandedSets.has(set.id)}
496
496
  {@const treeStore = treeStores.get(set.id)}
497
+ {@const hasPoints = set.cells.some(c => c.geometry === 'point')}
497
498
  <div class="set-section" class:expanded={isExpanded}>
498
499
  <!-- Set Header -->
499
500
  <div class="set-header d-flex align-items-center justify-content-between px-2 py-2">
@@ -512,6 +513,15 @@
512
513
  >
513
514
  <i class="bi bi-eye{set.visible ? '-fill text-primary' : '-slash text-muted'}"></i>
514
515
  </button>
516
+ <button
517
+ class="btn btn-sm p-0 me-2"
518
+ title={!hasPoints ? 'No points to label' : set.showLabels ? 'Hide Labels' : 'Show Labels'}
519
+ aria-label={!hasPoints ? 'No points to label' : set.showLabels ? 'Hide Labels' : 'Show Labels'}
520
+ onclick={() => setsStore.toggleSetLabels(set.id)}
521
+ disabled={!hasPoints}
522
+ >
523
+ <i class="bi bi-tag{!hasPoints ? ' text-muted opacity-50' : set.showLabels ? '-fill text-primary' : ' text-muted'}"></i>
524
+ </button>
515
525
  <div class="set-info text-truncate" onclick={() => toggleExpanded(set.id)} style="cursor: pointer;">
516
526
  <div class="fw-medium small text-truncate">{set.name}</div>
517
527
  <small class="text-muted">{getSetItemCounts(set)} · {set.groups.length} groups</small>
@@ -51,11 +51,13 @@
51
51
  cellFill: string;
52
52
  cellLine: string;
53
53
  pointCircle: string;
54
+ pointLabel: string;
54
55
  } {
55
56
  return {
56
57
  cellFill: `custom-cells-fill-${setId}`,
57
58
  cellLine: `custom-cells-line-${setId}`,
58
- pointCircle: `custom-points-circle-${setId}`
59
+ pointCircle: `custom-points-circle-${setId}`,
60
+ pointLabel: `custom-points-label-${setId}`
59
61
  };
60
62
  }
61
63
 
@@ -129,6 +131,30 @@
129
131
  });
130
132
  activeLayers.add(layers.pointCircle);
131
133
  }
134
+
135
+ // Add points label layer (symbol layer for text)
136
+ if (!map.getLayer(layers.pointLabel)) {
137
+ map.addLayer({
138
+ id: layers.pointLabel,
139
+ type: 'symbol',
140
+ source: sources.points,
141
+ layout: {
142
+ 'text-field': ['get', 'labelText'],
143
+ 'text-size': 11,
144
+ 'text-anchor': 'top',
145
+ 'text-offset': [0, 0.8],
146
+ 'text-allow-overlap': false,
147
+ 'text-ignore-placement': false,
148
+ 'visibility': set.showLabels ? 'visible' : 'none'
149
+ },
150
+ paint: {
151
+ 'text-color': '#333',
152
+ 'text-halo-color': '#fff',
153
+ 'text-halo-width': 1.5
154
+ }
155
+ });
156
+ activeLayers.add(layers.pointLabel);
157
+ }
132
158
  }
133
159
 
134
160
  /**
@@ -138,8 +164,8 @@
138
164
  const sources = getSourceIds(setIdToRemove);
139
165
  const layers = getLayerIds(setIdToRemove);
140
166
 
141
- // Remove layers
142
- for (const layerId of [layers.cellLine, layers.cellFill, layers.pointCircle]) {
167
+ // Remove layers (label layer must be removed first since it's on top)
168
+ for (const layerId of [layers.pointLabel, layers.cellLine, layers.cellFill, layers.pointCircle]) {
143
169
  if (map.getLayer(layerId)) {
144
170
  map.removeLayer(layerId);
145
171
  activeLayers.delete(layerId);
@@ -189,6 +215,9 @@
189
215
  // Darken color for stroke
190
216
  const strokeColor = darkenColor(color, 0.3);
191
217
 
218
+ // Generate label text from first 3 extra fields
219
+ const labelText = buildLabelText(item.extraFields);
220
+
192
221
  pointFeatures.push({
193
222
  type: 'Feature',
194
223
  geometry: {
@@ -203,7 +232,8 @@
203
232
  customGroup: item.customGroup,
204
233
  sizeFactor: item.sizeFactor,
205
234
  setName: set.name,
206
- popupInfo: { ...item.extraFields }
235
+ popupInfo: { ...item.extraFields },
236
+ labelText
207
237
  }
208
238
  });
209
239
  } else if (item.geometry === 'cell' && item.resolvedCell) {
@@ -259,9 +289,23 @@
259
289
  map.setPaintProperty(layers.pointCircle, 'circle-opacity', set.opacity);
260
290
  }
261
291
 
292
+ // Update label visibility
293
+ if (map.getLayer(layers.pointLabel)) {
294
+ map.setLayoutProperty(layers.pointLabel, 'visibility', set.showLabels ? 'visible' : 'none');
295
+ }
296
+
262
297
  console.log(`[CustomLayer] Rendered ${cellFeatures.length} cells, ${pointFeatures.length} points for set "${set.name}"`);
263
298
  }
264
299
 
300
+ /**
301
+ * Build label text from extra fields (first 3 values, newline separated)
302
+ */
303
+ function buildLabelText(extraFields: Record<string, string | number> | undefined): string {
304
+ if (!extraFields) return '';
305
+ const values = Object.values(extraFields).slice(0, 3);
306
+ return values.filter(v => v !== undefined && v !== null && v !== '').join('\n');
307
+ }
308
+
265
309
  /**
266
310
  * Darken a hex color by a factor
267
311
  */
@@ -138,14 +138,23 @@ export function parseCustomCellsCsv(csvContent, cellLookup, options = {}) {
138
138
  const groupsSet = new Set();
139
139
  let cellCount = 0;
140
140
  let pointCount = 0;
141
+ let autoIdCounter = 0;
141
142
  for (let i = 1; i < lines.length; i++) {
142
143
  const line = lines[i].trim();
143
144
  if (!line)
144
145
  continue;
145
146
  const values = parseCSVLine(line, actualDelimiter);
146
- const itemId = values[idIndex]?.trim();
147
- if (!itemId)
148
- continue;
147
+ // Get item ID - auto-generate for points if no ID column mapped
148
+ let itemId;
149
+ if (idIndex >= 0) {
150
+ itemId = values[idIndex]?.trim() || '';
151
+ if (!itemId)
152
+ continue; // Skip rows with empty ID when ID column is mapped
153
+ }
154
+ else {
155
+ // No ID column mapped - will auto-generate for points
156
+ itemId = '';
157
+ }
149
158
  // Get custom group (default to 'default')
150
159
  const customGroup = groupIndex !== -1
151
160
  ? (values[groupIndex]?.trim() || 'default')
@@ -187,8 +196,10 @@ export function parseCustomCellsCsv(csvContent, cellLookup, options = {}) {
187
196
  lat = parsedLat;
188
197
  lon = parsedLon;
189
198
  pointCount++;
199
+ // Auto-generate ID for points if not mapped
200
+ const pointId = itemId || `Point ${++autoIdCounter}`;
190
201
  cells.push({
191
- id: itemId,
202
+ id: pointId,
192
203
  customGroup,
193
204
  sizeFactor,
194
205
  extraFields,
@@ -199,7 +210,9 @@ export function parseCustomCellsCsv(csvContent, cellLookup, options = {}) {
199
210
  continue;
200
211
  }
201
212
  }
202
- // No valid lat/lon - try to resolve as cell
213
+ // No valid lat/lon - try to resolve as cell (requires ID)
214
+ if (!itemId)
215
+ continue; // Can't resolve cell without ID
203
216
  resolvedCell = cellLookup.get(itemId);
204
217
  if (resolvedCell) {
205
218
  cellCount++;
@@ -51,6 +51,10 @@ export declare class CustomCellSetsStore {
51
51
  * Toggle set visibility
52
52
  */
53
53
  toggleSetVisibility(setId: string): void;
54
+ /**
55
+ * Toggle set labels visibility
56
+ */
57
+ toggleSetLabels(setId: string): void;
54
58
  /**
55
59
  * Toggle group visibility within a set
56
60
  */
@@ -101,6 +101,7 @@ export class CustomCellSetsStore {
101
101
  defaultColor: CUSTOM_CELL_PALETTE[0],
102
102
  groupColors,
103
103
  visible: true,
104
+ showLabels: false,
104
105
  visibleGroups: new Set(importResult.groups)
105
106
  };
106
107
  this.sets.push(newSet);
@@ -136,6 +137,16 @@ export class CustomCellSetsStore {
136
137
  this.version++;
137
138
  }
138
139
  }
140
+ /**
141
+ * Toggle set labels visibility
142
+ */
143
+ toggleSetLabels(setId) {
144
+ const set = this.getSet(setId);
145
+ if (set) {
146
+ set.showLabels = !set.showLabels;
147
+ this.version++;
148
+ }
149
+ }
139
150
  /**
140
151
  * Toggle group visibility within a set
141
152
  */
@@ -227,6 +238,7 @@ export class CustomCellSetsStore {
227
238
  this.sets = data.map((setData) => ({
228
239
  ...setData,
229
240
  visibleGroups: new Set(setData.visibleGroups || []),
241
+ showLabels: setData.showLabels ?? false,
230
242
  cells: setData.cells || []
231
243
  }));
232
244
  // Re-resolve cells
@@ -81,6 +81,8 @@ export interface CustomCellSet {
81
81
  groupColors: Record<string, string>;
82
82
  /** Whether this set's layer is visible */
83
83
  visible: boolean;
84
+ /** Whether to show labels for points */
85
+ showLabels: boolean;
84
86
  /** Which groups are currently visible */
85
87
  visibleGroups: Set<string>;
86
88
  }
@@ -17,8 +17,11 @@ export declare function detectFieldType(header: string): StandardFieldType | nul
17
17
  /**
18
18
  * Auto-detect column mapping from headers
19
19
  * Returns suggested mapping based on alias matching
20
+ * @param headers - CSV column headers
21
+ * @param fieldsConfig - Field configuration for the import mode
22
+ * @param mode - Import mode (used to customize detection behavior)
20
23
  */
21
- export declare function detectColumnMapping(headers: string[], fieldsConfig: ImportFieldsConfig): ColumnMapping;
24
+ export declare function detectColumnMapping(headers: string[], fieldsConfig: ImportFieldsConfig, mode?: ImportMode): ColumnMapping;
22
25
  /**
23
26
  * Validate that all required fields are mapped
24
27
  */
@@ -50,7 +53,7 @@ export declare function parseHeaderLine(line: string, delimiter: ',' | ';'): str
50
53
  /**
51
54
  * Get CSV headers and suggested mapping
52
55
  */
53
- export declare function analyzeCSVHeaders(csvContent: string, fieldsConfig: ImportFieldsConfig): {
56
+ export declare function analyzeCSVHeaders(csvContent: string, fieldsConfig: ImportFieldsConfig, mode?: ImportMode): {
54
57
  headers: string[];
55
58
  delimiter: ',' | ';';
56
59
  suggestedMapping: ColumnMapping;
@@ -96,14 +96,21 @@ export function detectFieldType(header) {
96
96
  /**
97
97
  * Auto-detect column mapping from headers
98
98
  * Returns suggested mapping based on alias matching
99
+ * @param headers - CSV column headers
100
+ * @param fieldsConfig - Field configuration for the import mode
101
+ * @param mode - Import mode (used to customize detection behavior)
99
102
  */
100
- export function detectColumnMapping(headers, fieldsConfig) {
103
+ export function detectColumnMapping(headers, fieldsConfig, mode) {
101
104
  const mapping = {};
102
105
  const usedIndices = new Set();
103
106
  // Get all field types we're looking for
104
107
  const allFields = [...fieldsConfig.required, ...fieldsConfig.optional];
108
+ // For point mode, skip auto-detecting ID - default to auto-generated
109
+ const fieldsToDetect = mode === 'point'
110
+ ? allFields.filter(f => f.type !== 'id')
111
+ : allFields;
105
112
  // First pass: exact matches and strong matches
106
- for (const field of allFields) {
113
+ for (const field of fieldsToDetect) {
107
114
  const aliases = COLUMN_ALIASES[field.type] || [];
108
115
  for (let i = 0; i < headers.length; i++) {
109
116
  if (usedIndices.has(i))
@@ -118,7 +125,7 @@ export function detectColumnMapping(headers, fieldsConfig) {
118
125
  }
119
126
  }
120
127
  // Second pass: partial matches for unmapped fields
121
- for (const field of allFields) {
128
+ for (const field of fieldsToDetect) {
122
129
  if (mapping[field.type] !== undefined)
123
130
  continue;
124
131
  const aliases = COLUMN_ALIASES[field.type] || [];
@@ -190,7 +197,7 @@ export function detectImportMode(headers) {
190
197
  export function autoDetectImport(headers) {
191
198
  const mode = detectImportMode(headers);
192
199
  const fieldsConfig = getFieldsForMode(mode);
193
- const mapping = detectColumnMapping(headers, fieldsConfig);
200
+ const mapping = detectColumnMapping(headers, fieldsConfig, mode);
194
201
  return { mode, mapping, fieldsConfig };
195
202
  }
196
203
  /**
@@ -211,14 +218,14 @@ export function parseHeaderLine(line, delimiter) {
211
218
  /**
212
219
  * Get CSV headers and suggested mapping
213
220
  */
214
- export function analyzeCSVHeaders(csvContent, fieldsConfig) {
221
+ export function analyzeCSVHeaders(csvContent, fieldsConfig, mode) {
215
222
  const lines = csvContent.trim().split('\n');
216
223
  if (lines.length === 0) {
217
224
  return { headers: [], delimiter: ',', suggestedMapping: {}, rowCount: 0 };
218
225
  }
219
226
  const delimiter = detectDelimiter(lines[0]);
220
227
  const headers = parseHeaderLine(lines[0], delimiter);
221
- const suggestedMapping = detectColumnMapping(headers, fieldsConfig);
228
+ const suggestedMapping = detectColumnMapping(headers, fieldsConfig, mode);
222
229
  return {
223
230
  headers,
224
231
  delimiter,
@@ -9,7 +9,7 @@
9
9
  /** Cell mode - ID resolves against existing cell data */
10
10
  export const CELL_MODE_FIELDS = {
11
11
  required: [
12
- { type: 'id', label: 'Cell ID', required: true, description: 'e.g. cellName, txId' }
12
+ { type: 'id', label: 'txId / (Cell Name) ', required: true, description: 'Column to match against cell data' }
13
13
  ],
14
14
  optional: [
15
15
  { type: 'group', label: 'Group', required: false, description: 'e.g. customGroup, category' },
@@ -19,11 +19,11 @@ export const CELL_MODE_FIELDS = {
19
19
  /** Point mode - creates markers at lat/lon coordinates */
20
20
  export const POINT_MODE_FIELDS = {
21
21
  required: [
22
- { type: 'id', label: 'ID / Name', required: true, description: 'e.g. name, label, id' },
23
22
  { type: 'lat', label: 'Latitude', required: true, description: 'e.g. lat, latitude, y' },
24
23
  { type: 'lon', label: 'Longitude', required: true, description: 'e.g. lon, lng, longitude, x' }
25
24
  ],
26
25
  optional: [
26
+ { type: 'id', label: 'Name / ID', required: false, description: 'Optional - auto-generated if not mapped' },
27
27
  { type: 'group', label: 'Group', required: false, description: 'e.g. category, type' },
28
28
  { type: 'sizeFactor', label: 'Size', required: false, description: 'e.g. size, weight' }
29
29
  ]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smartnet360/svelte-components",
3
- "version": "0.0.145",
3
+ "version": "0.0.147",
4
4
  "scripts": {
5
5
  "dev": "vite dev",
6
6
  "build": "vite build && npm run prepack",