@smartnet360/svelte-components 0.0.144 → 0.0.145
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/apps/antenna-tools/components/AntennaTools.svelte +7 -3
- package/dist/map-v3/features/custom/components/CustomCellSetManager.svelte +15 -1
- package/dist/map-v3/features/custom/layers/CustomCellsLayer.svelte +2 -8
- package/dist/map-v3/features/custom/logic/csv-parser.d.ts +15 -3
- package/dist/map-v3/features/custom/logic/csv-parser.js +26 -10
- package/dist/map-v3/features/custom/stores/custom-cell-sets.svelte.d.ts +3 -3
- package/dist/map-v3/features/custom/stores/custom-cell-sets.svelte.js +4 -4
- package/dist/shared/csv-import/ColumnMapper.svelte +96 -2
- package/dist/shared/csv-import/ColumnMapper.svelte.d.ts +7 -0
- package/package.json +1 -1
|
@@ -110,11 +110,14 @@
|
|
|
110
110
|
if (externalAntenna1) {
|
|
111
111
|
const found = findAntennaBySpec(antennas, externalAntenna1);
|
|
112
112
|
if (found) {
|
|
113
|
+
// Compute available tilts INLINE to avoid stale state issue
|
|
114
|
+
const tiltsForAntenna1 = getAvailableTiltsForAntenna(found);
|
|
115
|
+
|
|
113
116
|
// Use untrack to prevent infinite loop from state updates
|
|
114
117
|
untrack(() => {
|
|
115
118
|
selectedAntenna = found;
|
|
116
|
-
|
|
117
|
-
ant1ElectricalTilt = findTiltIndex(
|
|
119
|
+
availableElectricalTilts = tiltsForAntenna1;
|
|
120
|
+
ant1ElectricalTilt = findTiltIndex(tiltsForAntenna1, externalAntenna1.electricalTilt ?? 0);
|
|
118
121
|
ant1MechanicalTilt = externalAntenna1.mechanicalTilt ?? 0;
|
|
119
122
|
});
|
|
120
123
|
} else {
|
|
@@ -126,9 +129,10 @@
|
|
|
126
129
|
if (externalAntenna2) {
|
|
127
130
|
const found2 = findAntennaBySpec(antennas, externalAntenna2);
|
|
128
131
|
if (found2) {
|
|
132
|
+
const tiltsForAntenna2 = getAvailableTiltsForAntenna(found2);
|
|
129
133
|
untrack(() => {
|
|
130
134
|
selectedAntenna2 = found2;
|
|
131
|
-
ant2ElectricalTilt = findTiltIndex(
|
|
135
|
+
ant2ElectricalTilt = findTiltIndex(tiltsForAntenna2, externalAntenna2.electricalTilt ?? 0);
|
|
132
136
|
ant2MechanicalTilt = externalAntenna2.mechanicalTilt ?? 0;
|
|
133
137
|
if (viewMode === 'single') {
|
|
134
138
|
viewMode = 'compare';
|
|
@@ -71,6 +71,8 @@
|
|
|
71
71
|
let csvHeaders = $state<string[]>([]);
|
|
72
72
|
let columnMapping = $state<ColumnMapping>({});
|
|
73
73
|
let importMode = $state<ImportMode>('cell');
|
|
74
|
+
let includeAllExtras = $state(true);
|
|
75
|
+
let selectedExtras = $state<number[]>([]);
|
|
74
76
|
|
|
75
77
|
// Quick Add state
|
|
76
78
|
let showQuickAdd = $state(false);
|
|
@@ -203,7 +205,11 @@
|
|
|
203
205
|
if (!pendingCsvContent) return;
|
|
204
206
|
|
|
205
207
|
try {
|
|
206
|
-
importResult = setsStore.importFromCsv(pendingCsvContent, importFileName,
|
|
208
|
+
importResult = setsStore.importFromCsv(pendingCsvContent, importFileName, {
|
|
209
|
+
columnMapping,
|
|
210
|
+
includeAllExtras,
|
|
211
|
+
selectedExtraIndices: selectedExtras
|
|
212
|
+
});
|
|
207
213
|
showMappingModal = false;
|
|
208
214
|
showImportModal = true;
|
|
209
215
|
} catch (err) {
|
|
@@ -219,6 +225,8 @@
|
|
|
219
225
|
csvHeaders = [];
|
|
220
226
|
columnMapping = {};
|
|
221
227
|
importMode = 'cell';
|
|
228
|
+
includeAllExtras = true;
|
|
229
|
+
selectedExtras = [];
|
|
222
230
|
}
|
|
223
231
|
|
|
224
232
|
function confirmImport() {
|
|
@@ -596,8 +604,14 @@
|
|
|
596
604
|
headers={csvHeaders}
|
|
597
605
|
mode={importMode}
|
|
598
606
|
mapping={columnMapping}
|
|
607
|
+
{includeAllExtras}
|
|
608
|
+
{selectedExtras}
|
|
599
609
|
onmodechange={(m) => importMode = m}
|
|
600
610
|
onchange={(m) => columnMapping = m}
|
|
611
|
+
onextraschange={(all, selected) => {
|
|
612
|
+
includeAllExtras = all;
|
|
613
|
+
selectedExtras = selected;
|
|
614
|
+
}}
|
|
601
615
|
/>
|
|
602
616
|
</div>
|
|
603
617
|
<div class="modal-footer py-2">
|
|
@@ -203,9 +203,7 @@
|
|
|
203
203
|
customGroup: item.customGroup,
|
|
204
204
|
sizeFactor: item.sizeFactor,
|
|
205
205
|
setName: set.name,
|
|
206
|
-
...
|
|
207
|
-
Object.entries(item.extraFields).map(([k, v]) => [`extra_${k}`, v])
|
|
208
|
-
)
|
|
206
|
+
popupInfo: { ...item.extraFields }
|
|
209
207
|
}
|
|
210
208
|
});
|
|
211
209
|
} else if (item.geometry === 'cell' && item.resolvedCell) {
|
|
@@ -226,11 +224,7 @@
|
|
|
226
224
|
feature.properties.customGroup = item.customGroup;
|
|
227
225
|
feature.properties.sizeFactor = item.sizeFactor;
|
|
228
226
|
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
|
-
}
|
|
227
|
+
feature.properties.popupInfo = { ...item.extraFields };
|
|
234
228
|
}
|
|
235
229
|
|
|
236
230
|
cellFeatures.push(feature);
|
|
@@ -26,15 +26,27 @@ export type CsvDelimiter = ',' | ';' | 'auto';
|
|
|
26
26
|
* @returns Array of header strings
|
|
27
27
|
*/
|
|
28
28
|
export declare function extractCsvHeaders(csvContent: string, delimiter?: CsvDelimiter): string[];
|
|
29
|
+
/**
|
|
30
|
+
* Options for CSV parsing
|
|
31
|
+
*/
|
|
32
|
+
export interface CsvParseOptions {
|
|
33
|
+
/** Delimiter to use: ',' (comma), ';' (semicolon), or 'auto' (detect) */
|
|
34
|
+
delimiter?: CsvDelimiter;
|
|
35
|
+
/** User-defined column mapping (overrides auto-detection) */
|
|
36
|
+
columnMapping?: ColumnMapping;
|
|
37
|
+
/** Whether to include all extra columns (default: true) */
|
|
38
|
+
includeAllExtras?: boolean;
|
|
39
|
+
/** Specific extra column indices to include (when includeAllExtras is false) */
|
|
40
|
+
selectedExtraIndices?: number[];
|
|
41
|
+
}
|
|
29
42
|
/**
|
|
30
43
|
* Parse a CSV string into custom items (cells or points)
|
|
31
44
|
* @param csvContent Raw CSV content
|
|
32
45
|
* @param cellLookup Map of cellName -> Cell for resolving cell data
|
|
33
|
-
* @param
|
|
34
|
-
* @param columnMapping Optional user-defined column mapping (overrides auto-detection)
|
|
46
|
+
* @param options Parsing options
|
|
35
47
|
* @returns Import result with items, unmatched IDs, groups, and extra columns
|
|
36
48
|
*/
|
|
37
|
-
export declare function parseCustomCellsCsv(csvContent: string, cellLookup: Map<string, Cell>,
|
|
49
|
+
export declare function parseCustomCellsCsv(csvContent: string, cellLookup: Map<string, Cell>, options?: CsvParseOptions): CustomCellImportResult;
|
|
38
50
|
/**
|
|
39
51
|
* Build a cell lookup map from an array of cells
|
|
40
52
|
* Creates lookups for both cellName and txId for flexible matching
|
|
@@ -49,11 +49,11 @@ export function extractCsvHeaders(csvContent, delimiter = 'auto') {
|
|
|
49
49
|
* Parse a CSV string into custom items (cells or points)
|
|
50
50
|
* @param csvContent Raw CSV content
|
|
51
51
|
* @param cellLookup Map of cellName -> Cell for resolving cell data
|
|
52
|
-
* @param
|
|
53
|
-
* @param columnMapping Optional user-defined column mapping (overrides auto-detection)
|
|
52
|
+
* @param options Parsing options
|
|
54
53
|
* @returns Import result with items, unmatched IDs, groups, and extra columns
|
|
55
54
|
*/
|
|
56
|
-
export function parseCustomCellsCsv(csvContent, cellLookup,
|
|
55
|
+
export function parseCustomCellsCsv(csvContent, cellLookup, options = {}) {
|
|
56
|
+
const { delimiter = 'auto', columnMapping, includeAllExtras = true, selectedExtraIndices = [] } = options;
|
|
57
57
|
const lines = csvContent.trim().split('\n');
|
|
58
58
|
if (lines.length < 2) {
|
|
59
59
|
return {
|
|
@@ -106,16 +106,32 @@ export function parseCustomCellsCsv(csvContent, cellLookup, delimiter = 'auto',
|
|
|
106
106
|
throw new Error('CSV must contain an "id", "cellName", or "txId" column');
|
|
107
107
|
}
|
|
108
108
|
const hasLatLon = latIndex !== -1 && lonIndex !== -1;
|
|
109
|
-
// Find extra columns
|
|
109
|
+
// Find extra columns based on user selection
|
|
110
110
|
const extraColumns = [];
|
|
111
111
|
const extraIndices = [];
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
112
|
+
// Get indices that are already mapped to known fields
|
|
113
|
+
const mappedIndices = new Set([idIndex, groupIndex, sizeFactorIndex, latIndex, lonIndex].filter(i => i >= 0));
|
|
114
|
+
if (includeAllExtras) {
|
|
115
|
+
// Include all non-reserved, non-mapped columns
|
|
116
|
+
headers.forEach((header, idx) => {
|
|
117
|
+
if (mappedIndices.has(idx))
|
|
118
|
+
return;
|
|
119
|
+
const normalized = normalizeColumnName(header);
|
|
120
|
+
if (!RESERVED_COLUMNS.includes(normalized)) {
|
|
121
|
+
extraColumns.push(header.trim());
|
|
122
|
+
extraIndices.push(idx);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
// Include only specifically selected columns
|
|
128
|
+
for (const idx of selectedExtraIndices) {
|
|
129
|
+
if (idx >= 0 && idx < headers.length && !mappedIndices.has(idx)) {
|
|
130
|
+
extraColumns.push(headers[idx].trim());
|
|
131
|
+
extraIndices.push(idx);
|
|
132
|
+
}
|
|
117
133
|
}
|
|
118
|
-
}
|
|
134
|
+
}
|
|
119
135
|
// Parse data rows
|
|
120
136
|
const cells = [];
|
|
121
137
|
const unmatchedTxIds = [];
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import type { Cell } from '../../../../shared/demo';
|
|
8
8
|
import type { CellDataStore } from '../../cells/stores/cell.data.svelte';
|
|
9
9
|
import type { CustomCellSet, CustomCellImportResult } from '../types';
|
|
10
|
-
import type
|
|
10
|
+
import { type CsvParseOptions } from '../logic/csv-parser';
|
|
11
11
|
/** Function that returns the current cells array */
|
|
12
12
|
type CellsGetter = () => Cell[];
|
|
13
13
|
/**
|
|
@@ -32,9 +32,9 @@ export declare class CustomCellSetsStore {
|
|
|
32
32
|
* Import a CSV file and create a new custom cell set
|
|
33
33
|
* @param csvContent Raw CSV content
|
|
34
34
|
* @param fileName Name of the file being imported
|
|
35
|
-
* @param
|
|
35
|
+
* @param options Parsing options (column mapping, extra fields selection)
|
|
36
36
|
*/
|
|
37
|
-
importFromCsv(csvContent: string, fileName: string,
|
|
37
|
+
importFromCsv(csvContent: string, fileName: string, options?: CsvParseOptions): CustomCellImportResult;
|
|
38
38
|
/**
|
|
39
39
|
* Create a new set from import result
|
|
40
40
|
*/
|
|
@@ -69,13 +69,13 @@ export class CustomCellSetsStore {
|
|
|
69
69
|
* Import a CSV file and create a new custom cell set
|
|
70
70
|
* @param csvContent Raw CSV content
|
|
71
71
|
* @param fileName Name of the file being imported
|
|
72
|
-
* @param
|
|
72
|
+
* @param options Parsing options (column mapping, extra fields selection)
|
|
73
73
|
*/
|
|
74
|
-
importFromCsv(csvContent, fileName,
|
|
74
|
+
importFromCsv(csvContent, fileName, options) {
|
|
75
75
|
// Build lookup from all cells
|
|
76
76
|
const cellLookup = buildCellLookup(this.getCells());
|
|
77
|
-
// Parse CSV with
|
|
78
|
-
const result = parseCustomCellsCsv(csvContent, cellLookup,
|
|
77
|
+
// Parse CSV with options
|
|
78
|
+
const result = parseCustomCellsCsv(csvContent, cellLookup, options || {});
|
|
79
79
|
return result;
|
|
80
80
|
}
|
|
81
81
|
/**
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Compact UI for mapping CSV columns to expected fields.
|
|
6
6
|
* Includes import mode selection (Cell/Point/Site) with auto-detection.
|
|
7
|
+
* Supports extra field selection for tooltips.
|
|
7
8
|
*/
|
|
8
9
|
import type { ColumnMapping, ImportFieldsConfig, ImportMode } from './types';
|
|
9
10
|
import { getFieldsForMode } from './types';
|
|
@@ -16,18 +17,27 @@
|
|
|
16
17
|
mode: ImportMode;
|
|
17
18
|
/** Current mapping (can be auto-detected or user-modified) */
|
|
18
19
|
mapping: ColumnMapping;
|
|
20
|
+
/** Whether to include all extra columns */
|
|
21
|
+
includeAllExtras?: boolean;
|
|
22
|
+
/** Selected extra column indices (when not including all) */
|
|
23
|
+
selectedExtras?: number[];
|
|
19
24
|
/** Called when mode changes */
|
|
20
25
|
onmodechange?: (mode: ImportMode) => void;
|
|
21
26
|
/** Called when mapping changes */
|
|
22
27
|
onchange?: (mapping: ColumnMapping) => void;
|
|
28
|
+
/** Called when extra fields settings change */
|
|
29
|
+
onextraschange?: (includeAll: boolean, selected: number[]) => void;
|
|
23
30
|
}
|
|
24
31
|
|
|
25
32
|
let {
|
|
26
33
|
headers,
|
|
27
34
|
mode,
|
|
28
35
|
mapping,
|
|
36
|
+
includeAllExtras = true,
|
|
37
|
+
selectedExtras = [],
|
|
29
38
|
onmodechange,
|
|
30
|
-
onchange
|
|
39
|
+
onchange,
|
|
40
|
+
onextraschange
|
|
31
41
|
}: Props = $props();
|
|
32
42
|
|
|
33
43
|
// Get fields config based on current mode
|
|
@@ -36,6 +46,18 @@
|
|
|
36
46
|
// All fields to display
|
|
37
47
|
let allFields = $derived([...fieldsConfig.required, ...fieldsConfig.optional]);
|
|
38
48
|
|
|
49
|
+
// Get mapped column indices (to exclude from extra fields options)
|
|
50
|
+
let mappedIndices = $derived(new Set(
|
|
51
|
+
Object.values(mapping).filter((v): v is number => v !== null && v !== undefined)
|
|
52
|
+
));
|
|
53
|
+
|
|
54
|
+
// Available columns for extra fields (not already mapped)
|
|
55
|
+
let availableExtras = $derived(
|
|
56
|
+
headers
|
|
57
|
+
.map((header, idx) => ({ header, idx }))
|
|
58
|
+
.filter(({ idx }) => !mappedIndices.has(idx))
|
|
59
|
+
);
|
|
60
|
+
|
|
39
61
|
// Validation state
|
|
40
62
|
let missingRequired = $derived(
|
|
41
63
|
fieldsConfig.required.filter(f =>
|
|
@@ -78,6 +100,35 @@
|
|
|
78
100
|
const idx = mapping[fieldType];
|
|
79
101
|
return idx !== null && idx !== undefined ? headers[idx] : null;
|
|
80
102
|
}
|
|
103
|
+
|
|
104
|
+
// Extra fields handlers
|
|
105
|
+
function handleIncludeAllChange(event: Event) {
|
|
106
|
+
const checked = (event.target as HTMLInputElement).checked;
|
|
107
|
+
onextraschange?.(checked, selectedExtras);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function handleExtraFieldChange(slotIndex: number, event: Event) {
|
|
111
|
+
const select = event.target as HTMLSelectElement;
|
|
112
|
+
const value = select.value;
|
|
113
|
+
|
|
114
|
+
const newSelected = [...selectedExtras];
|
|
115
|
+
|
|
116
|
+
if (value === '') {
|
|
117
|
+
// Remove this slot
|
|
118
|
+
newSelected[slotIndex] = -1;
|
|
119
|
+
} else {
|
|
120
|
+
newSelected[slotIndex] = parseInt(value, 10);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Filter out -1 values and duplicates
|
|
124
|
+
const filtered = newSelected.filter((v, i) => v >= 0 && newSelected.indexOf(v) === i);
|
|
125
|
+
onextraschange?.(includeAllExtras, filtered);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function getExtraSlotValue(slotIndex: number): string {
|
|
129
|
+
const value = selectedExtras[slotIndex];
|
|
130
|
+
return value === undefined || value < 0 ? '' : String(value);
|
|
131
|
+
}
|
|
81
132
|
</script>
|
|
82
133
|
|
|
83
134
|
<div class="column-mapper">
|
|
@@ -150,13 +201,56 @@
|
|
|
150
201
|
>
|
|
151
202
|
<option value="">{field.description || '-- Select --'}</option>
|
|
152
203
|
{#each headers as header, idx}
|
|
153
|
-
<option value={idx}>{header}</option>
|
|
204
|
+
<option value={String(idx)}>{header}</option>
|
|
154
205
|
{/each}
|
|
155
206
|
</select>
|
|
156
207
|
</div>
|
|
157
208
|
{/each}
|
|
158
209
|
</div>
|
|
159
210
|
|
|
211
|
+
<!-- Extra Fields Section -->
|
|
212
|
+
{#if availableExtras.length > 0}
|
|
213
|
+
<div class="extra-fields mt-3 pt-2 border-top">
|
|
214
|
+
<div class="d-flex align-items-center justify-content-between mb-2">
|
|
215
|
+
<span class="small fw-medium">Extra columns for popups</span>
|
|
216
|
+
<div class="form-check form-check-inline mb-0">
|
|
217
|
+
<input
|
|
218
|
+
class="form-check-input"
|
|
219
|
+
type="checkbox"
|
|
220
|
+
id="include-all-extras"
|
|
221
|
+
checked={includeAllExtras}
|
|
222
|
+
onchange={handleIncludeAllChange}
|
|
223
|
+
/>
|
|
224
|
+
<label class="form-check-label small" for="include-all-extras">
|
|
225
|
+
Include all ({availableExtras.length})
|
|
226
|
+
</label>
|
|
227
|
+
</div>
|
|
228
|
+
</div>
|
|
229
|
+
|
|
230
|
+
{#if !includeAllExtras}
|
|
231
|
+
<div class="extra-slots">
|
|
232
|
+
{#each [0, 1, 2] as slotIndex}
|
|
233
|
+
{@const slotId = `extra-slot-${slotIndex}`}
|
|
234
|
+
<div class="d-flex align-items-center gap-2 mb-1">
|
|
235
|
+
<select
|
|
236
|
+
id={slotId}
|
|
237
|
+
class="form-select form-select-sm"
|
|
238
|
+
value={getExtraSlotValue(slotIndex)}
|
|
239
|
+
onchange={(e) => handleExtraFieldChange(slotIndex, e)}
|
|
240
|
+
aria-label={`Extra field ${slotIndex + 1}`}
|
|
241
|
+
>
|
|
242
|
+
<option value="">-- Extra {slotIndex + 1} --</option>
|
|
243
|
+
{#each availableExtras as { header, idx }}
|
|
244
|
+
<option value={String(idx)}>{header}</option>
|
|
245
|
+
{/each}
|
|
246
|
+
</select>
|
|
247
|
+
</div>
|
|
248
|
+
{/each}
|
|
249
|
+
</div>
|
|
250
|
+
{/if}
|
|
251
|
+
</div>
|
|
252
|
+
{/if}
|
|
253
|
+
|
|
160
254
|
<!-- Validation Warning -->
|
|
161
255
|
{#if missingRequired.length > 0}
|
|
162
256
|
<div class="alert alert-warning py-1 px-2 mt-2 mb-0 small">
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Compact UI for mapping CSV columns to expected fields.
|
|
5
5
|
* Includes import mode selection (Cell/Point/Site) with auto-detection.
|
|
6
|
+
* Supports extra field selection for tooltips.
|
|
6
7
|
*/
|
|
7
8
|
import type { ColumnMapping, ImportMode } from './types';
|
|
8
9
|
interface Props {
|
|
@@ -12,10 +13,16 @@ interface Props {
|
|
|
12
13
|
mode: ImportMode;
|
|
13
14
|
/** Current mapping (can be auto-detected or user-modified) */
|
|
14
15
|
mapping: ColumnMapping;
|
|
16
|
+
/** Whether to include all extra columns */
|
|
17
|
+
includeAllExtras?: boolean;
|
|
18
|
+
/** Selected extra column indices (when not including all) */
|
|
19
|
+
selectedExtras?: number[];
|
|
15
20
|
/** Called when mode changes */
|
|
16
21
|
onmodechange?: (mode: ImportMode) => void;
|
|
17
22
|
/** Called when mapping changes */
|
|
18
23
|
onchange?: (mapping: ColumnMapping) => void;
|
|
24
|
+
/** Called when extra fields settings change */
|
|
25
|
+
onextraschange?: (includeAll: boolean, selected: number[]) => void;
|
|
19
26
|
}
|
|
20
27
|
declare const ColumnMapper: import("svelte").Component<Props, {}, "">;
|
|
21
28
|
type ColumnMapper = ReturnType<typeof ColumnMapper>;
|