@smartnet360/svelte-components 0.0.117 → 0.0.119

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.
@@ -50,8 +50,11 @@
50
50
  let selectedAntenna2 = $state<Antenna | null>(null);
51
51
 
52
52
  // External Bootstrap slider values
53
- let ant1ElectricalTilt = $state(initialEtilt1);
54
- let ant2ElectricalTilt = $state(initialEtilt2);
53
+ // Note: Electrical tilt is an INDEX into availableElectricalTilts array
54
+ // Mechanical tilt is a direct VALUE in degrees
55
+ // We initialize to 0 and set correct values in onMount after loading antenna data
56
+ let ant1ElectricalTilt = $state(0);
57
+ let ant2ElectricalTilt = $state(0);
55
58
  let ant1MechanicalTilt = $state(initialMtilt1);
56
59
  let ant2MechanicalTilt = $state(initialMtilt2);
57
60
 
@@ -72,6 +75,7 @@
72
75
  let availableElectricalTilts = $state<string[]>(['0', '2', '4', '6', '8']);
73
76
 
74
77
  // Handle antenna selection from controls
78
+ // Note: We no longer reset tilts here - tilts should persist unless explicitly changed
75
79
  function handleAntenna1Change(antenna: Antenna | null) {
76
80
  selectedAntenna = antenna;
77
81
  if (antenna) {
@@ -79,18 +83,11 @@
79
83
  availableElectricalTilts = antenna.tilt ?
80
84
  antenna.tilt.split(',').map(t => t.trim()) :
81
85
  ['0', '2', '4', '6', '8'];
82
-
83
- ant1ElectricalTilt = 0;
84
- ant1MechanicalTilt = 0;
85
86
  }
86
87
  }
87
88
 
88
89
  function handleAntenna2Change(antenna: Antenna | null) {
89
90
  selectedAntenna2 = antenna;
90
- if (antenna) {
91
- ant2ElectricalTilt = 0;
92
- ant2MechanicalTilt = 0;
93
- }
94
91
  }
95
92
 
96
93
  // Handle electrical tilt changes from controls
@@ -145,6 +142,12 @@
145
142
  }
146
143
  }
147
144
 
145
+ // Helper to find tilt index from actual tilt value
146
+ function findTiltIndex(tilts: string[], tiltValue: number): number {
147
+ const index = tilts.findIndex(t => parseInt(t, 10) === tiltValue);
148
+ return index >= 0 ? index : 0; // Default to 0 if not found
149
+ }
150
+
148
151
  onMount(async () => {
149
152
  try {
150
153
  // Load antenna data
@@ -153,30 +156,81 @@
153
156
  if (antennas.length > 0) {
154
157
  // Check if initial antenna names were provided (for external initialization)
155
158
  if (initialAntenna1Name) {
156
- // Find antenna by name (case-insensitive partial match)
157
- const found = antennas.find(a =>
158
- a.name.toLowerCase().includes(initialAntenna1Name.toLowerCase())
159
- );
159
+ // Try exact match first, then case-insensitive, then partial match
160
+ let found = antennas.find(a => a.name === initialAntenna1Name);
161
+ if (!found) {
162
+ found = antennas.find(a => a.name.toLowerCase() === initialAntenna1Name.toLowerCase());
163
+ }
164
+ if (!found) {
165
+ found = antennas.find(a => a.name.toLowerCase().includes(initialAntenna1Name.toLowerCase()));
166
+ }
167
+ if (!found) {
168
+ console.warn(`[AntennaDiagrams] Antenna not found: "${initialAntenna1Name}", using first available`);
169
+ }
160
170
  selectedAntenna = found || antennas[0];
161
171
  } else {
162
172
  selectedAntenna = antennas[0];
163
173
  }
164
174
 
165
- if (selectedAntenna.tilt) {
166
- availableElectricalTilts = selectedAntenna.tilt.split(',').map(t => t.trim());
175
+ // Collect ALL available tilts from all antennas with the same name
176
+ if (selectedAntenna) {
177
+ const sameName = antennas.filter(a => a.name === selectedAntenna!.name);
178
+ const allTilts = new Set<string>();
179
+ sameName.forEach(a => {
180
+ if (a.tilt) {
181
+ const tiltStr = a.tilt.toString();
182
+ if (tiltStr.includes(',')) {
183
+ tiltStr.split(',').forEach(t => allTilts.add(t.trim()));
184
+ } else {
185
+ allTilts.add(tiltStr.trim());
186
+ }
187
+ }
188
+ });
189
+ availableElectricalTilts = Array.from(allTilts).sort((a, b) => parseFloat(a) - parseFloat(b));
167
190
  }
168
191
 
192
+ // Set initial electrical tilt for antenna 1 (convert value to index)
193
+ ant1ElectricalTilt = findTiltIndex(availableElectricalTilts, initialEtilt1);
194
+
169
195
  // Set antenna 2 selection (for compare mode)
170
196
  if (initialAntenna2Name) {
171
- const found = antennas.find(a =>
172
- a.name.toLowerCase().includes(initialAntenna2Name.toLowerCase())
173
- );
197
+ // Try exact match first, then case-insensitive, then partial match
198
+ let found = antennas.find(a => a.name === initialAntenna2Name);
199
+ if (!found) {
200
+ found = antennas.find(a => a.name.toLowerCase() === initialAntenna2Name.toLowerCase());
201
+ }
202
+ if (!found) {
203
+ found = antennas.find(a => a.name.toLowerCase().includes(initialAntenna2Name.toLowerCase()));
204
+ }
205
+ if (!found) {
206
+ console.warn(`[AntennaDiagrams] Antenna 2 not found: "${initialAntenna2Name}", using fallback`);
207
+ }
174
208
  selectedAntenna2 = found || (antennas.length > 1 ? antennas[1] : antennas[0]);
175
209
  } else if (antennas.length > 1) {
176
210
  selectedAntenna2 = antennas[1];
177
211
  } else {
178
212
  selectedAntenna2 = antennas[0]; // Use same antenna if only one available
179
213
  }
214
+
215
+ // Set initial electrical tilt for antenna 2 (convert value to index)
216
+ if (selectedAntenna2) {
217
+ // Collect ALL available tilts from all antennas with the same name
218
+ const sameName2 = antennas.filter(a => a.name === selectedAntenna2!.name);
219
+ const allTilts2 = new Set<string>();
220
+ sameName2.forEach(a => {
221
+ if (a.tilt) {
222
+ const tiltStr = a.tilt.toString();
223
+ if (tiltStr.includes(',')) {
224
+ tiltStr.split(',').forEach(t => allTilts2.add(t.trim()));
225
+ } else {
226
+ allTilts2.add(tiltStr.trim());
227
+ }
228
+ }
229
+ });
230
+ const ant2Tilts = Array.from(allTilts2).sort((a, b) => parseFloat(a) - parseFloat(b));
231
+ ant2ElectricalTilt = findTiltIndex(ant2Tilts, initialEtilt2);
232
+ }
233
+ // Mechanical tilt is a direct value, already set from prop
180
234
  }
181
235
  } catch (error) {
182
236
  console.error('Failed to initialize:', error);
@@ -0,0 +1,412 @@
1
+ <svelte:options runes={true} />
2
+
3
+ <script lang="ts">
4
+ import { onMount } from 'svelte';
5
+ import { loadAntennas } from '../utils/db-utils';
6
+ import type { Antenna } from '../db';
7
+ import type {
8
+ ViewMode,
9
+ PatternType,
10
+ PatternDisplayMode,
11
+ ChartEngineType,
12
+ AntennaCompareInit
13
+ } from '../types';
14
+ import {
15
+ findAntennaByName,
16
+ collectAvailableTilts,
17
+ findTiltIndex
18
+ } from '../utils/antenna-helpers';
19
+ import AntennaControls from './AntennaControls.svelte';
20
+ import AntennaSettingsModal from './AntennaSettingsModal.svelte';
21
+ import { PolarLineChart, PolarAreaChart } from './chart-engines/index';
22
+
23
+ // ─────────────────────────────────────────────────────────────────────────────
24
+ // PROPS - Optional initialization from external sources
25
+ // ─────────────────────────────────────────────────────────────────────────────
26
+
27
+ interface Props {
28
+ /** Initial configuration for comparison mode */
29
+ init?: AntennaCompareInit;
30
+ }
31
+
32
+ let { init }: Props = $props();
33
+
34
+ // ─────────────────────────────────────────────────────────────────────────────
35
+ // STATE - Core reactive state
36
+ // ─────────────────────────────────────────────────────────────────────────────
37
+
38
+ // Data
39
+ let antennas = $state<Antenna[]>([]);
40
+ let isLoading = $state(true);
41
+ let error = $state<string | null>(null);
42
+
43
+ // Antenna 1 selection
44
+ let selectedAntenna1 = $state<Antenna | null>(null);
45
+ let ant1ElectricalTiltIndex = $state(0);
46
+ let ant1MechanicalTilt = $state(0);
47
+ let ant1AvailableTilts = $state<string[]>(['0']);
48
+
49
+ // Antenna 2 selection
50
+ let selectedAntenna2 = $state<Antenna | null>(null);
51
+ let ant2ElectricalTiltIndex = $state(0);
52
+ let ant2MechanicalTilt = $state(0);
53
+ let ant2AvailableTilts = $state<string[]>(['0']);
54
+
55
+ // View settings
56
+ let viewMode = $state<ViewMode>('single');
57
+ let patternType = $state<PatternType>('vertical');
58
+ let displayMode = $state<PatternDisplayMode>('normalized');
59
+ let chartEngine = $state<ChartEngineType>('polar-line');
60
+
61
+ // Settings modal
62
+ let showSettingsModal = $state(false);
63
+
64
+ // ─────────────────────────────────────────────────────────────────────────────
65
+ // DERIVED - Computed values
66
+ // ─────────────────────────────────────────────────────────────────────────────
67
+
68
+ /** Generate chart title based on current selection */
69
+ let chartTitle = $derived.by(() => {
70
+ if (viewMode === 'compare' && selectedAntenna1 && selectedAntenna2) {
71
+ const label1 = init?.label1 || selectedAntenna1.name;
72
+ const label2 = init?.label2 || selectedAntenna2.name;
73
+ return `${label1} vs ${label2}`;
74
+ }
75
+
76
+ if (selectedAntenna1) {
77
+ const label = init?.label1 || selectedAntenna1.name;
78
+ return `${label} - Pattern Analysis`;
79
+ }
80
+
81
+ return 'Antenna Pattern Analysis';
82
+ });
83
+
84
+ // ─────────────────────────────────────────────────────────────────────────────
85
+ // INITIALIZATION
86
+ // ─────────────────────────────────────────────────────────────────────────────
87
+
88
+ onMount(async () => {
89
+ try {
90
+ isLoading = true;
91
+ antennas = await loadAntennas();
92
+
93
+ if (antennas.length === 0) {
94
+ error = 'No antenna data found. Please load antenna files.';
95
+ return;
96
+ }
97
+
98
+ // Initialize with provided config or defaults
99
+ initializeAntennas();
100
+
101
+ } catch (e) {
102
+ error = e instanceof Error ? e.message : 'Failed to load antenna data';
103
+ console.error('[AntennaDiagrams] Initialization error:', e);
104
+ } finally {
105
+ isLoading = false;
106
+ }
107
+ });
108
+
109
+ /**
110
+ * Initialize antenna selections from init props or defaults
111
+ */
112
+ function initializeAntennas() {
113
+ // Set view mode from init or default
114
+ viewMode = init?.viewMode || 'single';
115
+
116
+ // Initialize Antenna 1
117
+ if (init?.antenna1Name) {
118
+ const result = findAntennaByName(antennas, init.antenna1Name);
119
+ selectedAntenna1 = result.antenna || antennas[0];
120
+
121
+ if (result.matchType === 'not-found') {
122
+ console.warn(`[AntennaDiagrams] Antenna 1 not found: "${init.antenna1Name}"`);
123
+ }
124
+ } else {
125
+ selectedAntenna1 = antennas[0];
126
+ }
127
+
128
+ // Collect available tilts for antenna 1
129
+ if (selectedAntenna1) {
130
+ ant1AvailableTilts = collectAvailableTilts(antennas, selectedAntenna1.name);
131
+ ant1ElectricalTiltIndex = findTiltIndex(ant1AvailableTilts, init?.etilt1 || 0);
132
+ ant1MechanicalTilt = init?.mtilt1 || 0;
133
+ }
134
+
135
+ // Initialize Antenna 2
136
+ if (init?.antenna2Name) {
137
+ const result = findAntennaByName(antennas, init.antenna2Name);
138
+ selectedAntenna2 = result.antenna || (antennas.length > 1 ? antennas[1] : antennas[0]);
139
+
140
+ if (result.matchType === 'not-found') {
141
+ console.warn(`[AntennaDiagrams] Antenna 2 not found: "${init.antenna2Name}"`);
142
+ }
143
+ } else {
144
+ selectedAntenna2 = antennas.length > 1 ? antennas[1] : antennas[0];
145
+ }
146
+
147
+ // Collect available tilts for antenna 2
148
+ if (selectedAntenna2) {
149
+ ant2AvailableTilts = collectAvailableTilts(antennas, selectedAntenna2.name);
150
+ ant2ElectricalTiltIndex = findTiltIndex(ant2AvailableTilts, init?.etilt2 || 0);
151
+ ant2MechanicalTilt = init?.mtilt2 || 0;
152
+ }
153
+ }
154
+
155
+ // ─────────────────────────────────────────────────────────────────────────────
156
+ // EVENT HANDLERS
157
+ // ─────────────────────────────────────────────────────────────────────────────
158
+
159
+ function handleAntenna1Change(antenna: Antenna | null) {
160
+ selectedAntenna1 = antenna;
161
+ if (antenna) {
162
+ ant1AvailableTilts = collectAvailableTilts(antennas, antenna.name);
163
+ }
164
+ }
165
+
166
+ function handleAntenna2Change(antenna: Antenna | null) {
167
+ selectedAntenna2 = antenna;
168
+ if (antenna) {
169
+ ant2AvailableTilts = collectAvailableTilts(antennas, antenna.name);
170
+ }
171
+ }
172
+
173
+ function handleTilt1Change(tiltIndex: number) {
174
+ ant1ElectricalTiltIndex = tiltIndex;
175
+ }
176
+
177
+ function handleTilt2Change(tiltIndex: number) {
178
+ ant2ElectricalTiltIndex = tiltIndex;
179
+ }
180
+
181
+ function handleMechTilt1Change(tilt: number) {
182
+ ant1MechanicalTilt = tilt;
183
+ }
184
+
185
+ function handleMechTilt2Change(tilt: number) {
186
+ ant2MechanicalTilt = tilt;
187
+ }
188
+
189
+ async function handleDataRefresh() {
190
+ try {
191
+ antennas = await loadAntennas();
192
+ if (antennas.length > 0) {
193
+ selectedAntenna1 = antennas[0];
194
+ selectedAntenna2 = antennas.length > 1 ? antennas[1] : antennas[0];
195
+ ant1ElectricalTiltIndex = 0;
196
+ ant2ElectricalTiltIndex = 0;
197
+ ant1MechanicalTilt = 0;
198
+ ant2MechanicalTilt = 0;
199
+ }
200
+ } catch (e) {
201
+ console.error('[AntennaDiagrams] Failed to refresh data:', e);
202
+ }
203
+ }
204
+ </script>
205
+
206
+ <!-- ═══════════════════════════════════════════════════════════════════════════ -->
207
+ <!-- TEMPLATE -->
208
+ <!-- ═══════════════════════════════════════════════════════════════════════════ -->
209
+
210
+ <div class="container-fluid mt-4">
211
+
212
+ {#if isLoading}
213
+ <!-- Loading State -->
214
+ <div class="text-center py-5">
215
+ <div class="spinner-border text-primary" role="status">
216
+ <span class="visually-hidden">Loading...</span>
217
+ </div>
218
+ <p class="mt-3 text-muted">Loading antenna data...</p>
219
+ </div>
220
+
221
+ {:else if error}
222
+ <!-- Error State -->
223
+ <div class="alert alert-warning d-flex align-items-center" role="alert">
224
+ <i class="bi bi-exclamation-triangle-fill me-2"></i>
225
+ <div>
226
+ {error}
227
+ <button type="button" class="btn btn-sm btn-outline-primary ms-3" onclick={() => showSettingsModal = true}>
228
+ Load Antenna Data
229
+ </button>
230
+ </div>
231
+ </div>
232
+
233
+ {:else}
234
+ <!-- Main Content -->
235
+
236
+ <!-- Control Bar -->
237
+ <div class="row mb-3 g-2">
238
+
239
+ <!-- View Mode -->
240
+ <div class="col-auto">
241
+ <div class="btn-group" role="group" aria-label="View mode">
242
+ <input type="radio" class="btn-check" id="viewSingle" bind:group={viewMode} value="single">
243
+ <label class="btn btn-outline-primary" for="viewSingle">
244
+ <i class="bi bi-square"></i> Single
245
+ </label>
246
+ <input type="radio" class="btn-check" id="viewCompare" bind:group={viewMode} value="compare">
247
+ <label class="btn btn-outline-primary" for="viewCompare">
248
+ <i class="bi bi-columns"></i> Compare
249
+ </label>
250
+ </div>
251
+ </div>
252
+
253
+ <!-- Chart Type -->
254
+ <div class="col-auto">
255
+ <div class="btn-group" role="group" aria-label="Chart type">
256
+ <input type="radio" class="btn-check" id="chartLine" bind:group={chartEngine} value="polar-line">
257
+ <label class="btn btn-outline-secondary" for="chartLine">Line</label>
258
+ <input type="radio" class="btn-check" id="chartArea" bind:group={chartEngine} value="polar-area">
259
+ <label class="btn btn-outline-secondary" for="chartArea">Area</label>
260
+ </div>
261
+ </div>
262
+
263
+ <!-- Display Mode -->
264
+ <div class="col-auto">
265
+ <div class="btn-group" role="group" aria-label="Display mode">
266
+ <input type="radio" class="btn-check" id="dispPattern" bind:group={displayMode} value="normalized">
267
+ <label class="btn btn-outline-secondary" for="dispPattern">Pattern</label>
268
+ <input type="radio" class="btn-check" id="dispGain" bind:group={displayMode} value="gain-adjusted">
269
+ <label class="btn btn-outline-secondary" for="dispGain">+Gain</label>
270
+ </div>
271
+ </div>
272
+
273
+ <!-- Pattern Type -->
274
+ <div class="col-auto">
275
+ <div class="btn-group" role="group" aria-label="Pattern type">
276
+ <input type="radio" class="btn-check" id="patHoriz" bind:group={patternType} value="horizontal">
277
+ <label class="btn btn-outline-secondary" for="patHoriz">Horizontal</label>
278
+ <input type="radio" class="btn-check" id="patVert" bind:group={patternType} value="vertical">
279
+ <label class="btn btn-outline-secondary" for="patVert">Vertical</label>
280
+ </div>
281
+ </div>
282
+
283
+ <!-- Spacer -->
284
+ <div class="col"></div>
285
+
286
+ <!-- Settings Button -->
287
+ <div class="col-auto">
288
+ <button
289
+ type="button"
290
+ class="btn btn-outline-secondary"
291
+ onclick={() => showSettingsModal = true}
292
+ aria-label="Antenna Settings"
293
+ >
294
+ <i class="bi bi-gear"></i>
295
+ </button>
296
+ </div>
297
+ </div>
298
+
299
+ <!-- Main Layout: Controls | Chart | Controls -->
300
+ <div class="row">
301
+
302
+ <!-- Antenna 1 Controls -->
303
+ <div class="col-lg-3 col-md-4">
304
+ <AntennaControls
305
+ {antennas}
306
+ selectedAntenna={selectedAntenna1}
307
+ antennaNumber={1}
308
+ electricalTiltIndex={ant1ElectricalTiltIndex}
309
+ mechanicalTilt={ant1MechanicalTilt}
310
+ colorTheme="primary"
311
+ onAntennaChange={handleAntenna1Change}
312
+ onElectricalTiltChange={handleTilt1Change}
313
+ onMechanicalTiltChange={handleMechTilt1Change}
314
+ />
315
+ </div>
316
+
317
+ <!-- Chart -->
318
+ <div class="col-lg-6 col-md-4">
319
+ <div class="chart-card">
320
+ {#if chartEngine === 'polar-line'}
321
+ <PolarLineChart
322
+ selectedAntenna={selectedAntenna1}
323
+ selectedAntenna2={selectedAntenna2}
324
+ {viewMode}
325
+ {patternType}
326
+ patternDisplayMode={displayMode}
327
+ ant1ElectricalTilt={ant1ElectricalTiltIndex}
328
+ ant1MechanicalTilt={ant1MechanicalTilt}
329
+ ant2ElectricalTilt={ant2ElectricalTiltIndex}
330
+ ant2MechanicalTilt={ant2MechanicalTilt}
331
+ title={chartTitle}
332
+ />
333
+ {:else if chartEngine === 'polar-area'}
334
+ <PolarAreaChart
335
+ selectedAntenna={selectedAntenna1}
336
+ selectedAntenna2={selectedAntenna2}
337
+ {viewMode}
338
+ {patternType}
339
+ patternDisplayMode={displayMode}
340
+ ant1ElectricalTilt={ant1ElectricalTiltIndex}
341
+ ant1MechanicalTilt={ant1MechanicalTilt}
342
+ ant2ElectricalTilt={ant2ElectricalTiltIndex}
343
+ ant2MechanicalTilt={ant2MechanicalTilt}
344
+ title={chartTitle}
345
+ />
346
+ {/if}
347
+ </div>
348
+ </div>
349
+
350
+ <!-- Antenna 2 Controls (Compare Mode) -->
351
+ <div class="col-lg-3 col-md-4">
352
+ {#if viewMode === 'compare'}
353
+ <AntennaControls
354
+ {antennas}
355
+ selectedAntenna={selectedAntenna2}
356
+ antennaNumber={2}
357
+ electricalTiltIndex={ant2ElectricalTiltIndex}
358
+ mechanicalTilt={ant2MechanicalTilt}
359
+ colorTheme="warning"
360
+ onAntennaChange={handleAntenna2Change}
361
+ onElectricalTiltChange={handleTilt2Change}
362
+ onMechanicalTiltChange={handleMechTilt2Change}
363
+ />
364
+ {:else}
365
+ <!-- Placeholder to maintain layout -->
366
+ <div class="empty-placeholder"></div>
367
+ {/if}
368
+ </div>
369
+ </div>
370
+ {/if}
371
+ </div>
372
+
373
+ <!-- Settings Modal -->
374
+ <AntennaSettingsModal
375
+ show={showSettingsModal}
376
+ onClose={() => showSettingsModal = false}
377
+ on:dataRefresh={handleDataRefresh}
378
+ />
379
+
380
+ <!-- ═══════════════════════════════════════════════════════════════════════════ -->
381
+ <!-- STYLES -->
382
+ <!-- ═══════════════════════════════════════════════════════════════════════════ -->
383
+
384
+ <style>
385
+ .chart-card {
386
+ background: white;
387
+ border-radius: 12px;
388
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
389
+ padding: 0.5rem;
390
+ }
391
+
392
+ .empty-placeholder {
393
+ min-height: 200px;
394
+ }
395
+
396
+ /* Bootstrap icon font (if not already loaded) */
397
+ .bi::before {
398
+ vertical-align: -0.125em;
399
+ }
400
+
401
+ /* Enhanced button group styling */
402
+ .btn-group .btn {
403
+ font-size: 0.875rem;
404
+ }
405
+
406
+ /* Responsive adjustments */
407
+ @media (max-width: 992px) {
408
+ .chart-card {
409
+ margin-bottom: 1rem;
410
+ }
411
+ }
412
+ </style>
@@ -0,0 +1,8 @@
1
+ import type { AntennaCompareInit } from '../types';
2
+ interface Props {
3
+ /** Initial configuration for comparison mode */
4
+ init?: AntennaCompareInit;
5
+ }
6
+ declare const AntennaDiagramsV2: import("svelte").Component<Props, {}, "">;
7
+ type AntennaDiagramsV2 = ReturnType<typeof AntennaDiagramsV2>;
8
+ export default AntennaDiagramsV2;
@@ -1,6 +1,7 @@
1
1
  export { default as PlotlyRadarChart } from './components/PlotlyRadarChart.svelte';
2
2
  export { default as AntennaControls } from './components/AntennaControls.svelte';
3
3
  export { default as AntennaDiagrams } from './components/AntennaDiagrams.svelte';
4
+ export { default as AntennaDiagramsV2 } from './components/AntennaDiagramsV2.svelte';
4
5
  export { default as AntennaSettingsModal } from './components/AntennaSettingsModal.svelte';
5
6
  export { default as JsonImporter } from './components/JsonImporter.svelte';
6
7
  export { default as MSIConverter } from './components/MSIConverter.svelte';
@@ -10,8 +11,10 @@ export { PolarLineChart, PolarBarChart } from './components/chart-engines/index'
10
11
  export { antennas, selectedAntenna, searchQuery, filteredAntennas } from './stores/antennas';
11
12
  export { dbStatus, dataOperationStatus, updateDbStatus, trackDataOperation } from './stores/db-status';
12
13
  export { db, type Antenna } from './db';
14
+ export * from './types';
13
15
  export * from './utils/db-utils';
14
16
  export * from './utils/init-db';
15
17
  export * from './utils/msi-parser';
16
18
  export * from './utils/plotly-chart-utils';
17
19
  export * from './utils/load-static-antennas';
20
+ export * from './utils/antenna-helpers';
@@ -2,6 +2,7 @@
2
2
  export { default as PlotlyRadarChart } from './components/PlotlyRadarChart.svelte';
3
3
  export { default as AntennaControls } from './components/AntennaControls.svelte';
4
4
  export { default as AntennaDiagrams } from './components/AntennaDiagrams.svelte';
5
+ export { default as AntennaDiagramsV2 } from './components/AntennaDiagramsV2.svelte';
5
6
  export { default as AntennaSettingsModal } from './components/AntennaSettingsModal.svelte';
6
7
  export { default as JsonImporter } from './components/JsonImporter.svelte';
7
8
  export { default as MSIConverter } from './components/MSIConverter.svelte';
@@ -14,9 +15,12 @@ export { antennas, selectedAntenna, searchQuery, filteredAntennas } from './stor
14
15
  export { dbStatus, dataOperationStatus, updateDbStatus, trackDataOperation } from './stores/db-status';
15
16
  // Database and types
16
17
  export { db } from './db';
18
+ // Types
19
+ export * from './types';
17
20
  // Utilities
18
21
  export * from './utils/db-utils';
19
22
  export * from './utils/init-db';
20
23
  export * from './utils/msi-parser';
21
24
  export * from './utils/plotly-chart-utils';
22
25
  export * from './utils/load-static-antennas';
26
+ export * from './utils/antenna-helpers';
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Antenna Pattern Types
3
+ * Clean, centralized type definitions for the antenna pattern module
4
+ */
5
+ import type { Antenna } from './db';
6
+ /** View mode for antenna diagrams */
7
+ export type ViewMode = 'single' | 'compare';
8
+ /** Pattern type for radiation display */
9
+ export type PatternType = 'horizontal' | 'vertical';
10
+ /** Display mode for pattern visualization */
11
+ export type PatternDisplayMode = 'normalized' | 'gain-adjusted';
12
+ /** Chart engine type */
13
+ export type ChartEngineType = 'polar-line' | 'polar-bar' | 'polar-area';
14
+ /** Color theme for antenna controls */
15
+ export type ColorTheme = 'primary' | 'warning' | 'success' | 'info' | 'secondary';
16
+ /**
17
+ * Antenna selection state - tracks antenna, tilts, and available options
18
+ */
19
+ export interface AntennaSelection {
20
+ /** The selected antenna record */
21
+ antenna: Antenna | null;
22
+ /** Index into availableTilts array for electrical tilt */
23
+ electricalTiltIndex: number;
24
+ /** Mechanical tilt value in degrees */
25
+ mechanicalTilt: number;
26
+ /** Available electrical tilt values for this antenna model */
27
+ availableTilts: string[];
28
+ /** Available frequencies for this antenna model */
29
+ availableFrequencies: number[];
30
+ /** Custom label for display (e.g., cell name) */
31
+ label?: string;
32
+ }
33
+ /**
34
+ * Props for initializing AntennaDiagrams from external sources (URL params, etc.)
35
+ */
36
+ export interface AntennaCompareInit {
37
+ /** Antenna 1 name pattern to match */
38
+ antenna1Name?: string;
39
+ /** Antenna 2 name pattern to match */
40
+ antenna2Name?: string;
41
+ /** Electrical tilt VALUE for antenna 1 */
42
+ etilt1?: number;
43
+ /** Electrical tilt VALUE for antenna 2 */
44
+ etilt2?: number;
45
+ /** Mechanical tilt for antenna 1 */
46
+ mtilt1?: number;
47
+ /** Mechanical tilt for antenna 2 */
48
+ mtilt2?: number;
49
+ /** Initial view mode */
50
+ viewMode?: ViewMode;
51
+ /** Custom label for antenna 1 */
52
+ label1?: string;
53
+ /** Custom label for antenna 2 */
54
+ label2?: string;
55
+ }
56
+ /**
57
+ * Result of finding an antenna by name
58
+ */
59
+ export interface AntennaSearchResult {
60
+ antenna: Antenna | null;
61
+ matchType: 'exact' | 'case-insensitive' | 'partial' | 'not-found';
62
+ }
63
+ /**
64
+ * Complete state for the antenna diagram visualization
65
+ */
66
+ export interface AntennaDiagramState {
67
+ /** All loaded antennas */
68
+ antennas: Antenna[];
69
+ /** First antenna selection */
70
+ selection1: AntennaSelection;
71
+ /** Second antenna selection (for compare mode) */
72
+ selection2: AntennaSelection;
73
+ /** Current view mode */
74
+ viewMode: ViewMode;
75
+ /** Current pattern type */
76
+ patternType: PatternType;
77
+ /** Current display mode */
78
+ displayMode: PatternDisplayMode;
79
+ /** Current chart engine */
80
+ chartEngine: ChartEngineType;
81
+ /** Whether data is loading */
82
+ isLoading: boolean;
83
+ /** Error message if any */
84
+ error: string | null;
85
+ }
86
+ /** Re-export Antenna type for convenience */
87
+ export type { Antenna } from './db';
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Antenna Pattern Types
3
+ * Clean, centralized type definitions for the antenna pattern module
4
+ */
5
+ export {};
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Antenna Data Utilities
3
+ * Helper functions for working with antenna data, tilts, and searches
4
+ */
5
+ import type { Antenna, AntennaSearchResult } from '../types';
6
+ /**
7
+ * Find an antenna by name with fallback matching strategies
8
+ * @param antennas - Array of all antennas
9
+ * @param name - Name pattern to search for
10
+ * @returns Search result with antenna and match type
11
+ */
12
+ export declare function findAntennaByName(antennas: Antenna[], name: string): AntennaSearchResult;
13
+ /**
14
+ * Collect all available electrical tilts for an antenna model
15
+ * Searches all antennas with the same name and extracts unique tilt values
16
+ * @param antennas - Array of all antennas
17
+ * @param antennaName - The antenna model name
18
+ * @param frequency - Optional frequency to filter by
19
+ * @returns Sorted array of tilt values as strings
20
+ */
21
+ export declare function collectAvailableTilts(antennas: Antenna[], antennaName: string, frequency?: number | null): string[];
22
+ /**
23
+ * Collect all available frequencies for an antenna model
24
+ * @param antennas - Array of all antennas
25
+ * @param antennaName - The antenna model name
26
+ * @returns Sorted array of unique frequencies
27
+ */
28
+ export declare function collectAvailableFrequencies(antennas: Antenna[], antennaName: string): number[];
29
+ /**
30
+ * Find the index of a tilt value in the available tilts array
31
+ * @param availableTilts - Array of available tilt values
32
+ * @param tiltValue - The tilt value to find (as number)
33
+ * @returns Index in the array, or 0 if not found
34
+ */
35
+ export declare function findTiltIndex(availableTilts: string[], tiltValue: number): number;
36
+ /**
37
+ * Get the tilt value at a given index
38
+ * @param availableTilts - Array of available tilt values
39
+ * @param index - Index to retrieve
40
+ * @returns Tilt value as number
41
+ */
42
+ export declare function getTiltValue(availableTilts: string[], index: number): number;
43
+ /**
44
+ * Find an antenna with specific name, frequency, and tilt
45
+ * @param antennas - Array of all antennas
46
+ * @param name - Antenna model name
47
+ * @param frequency - Target frequency (optional)
48
+ * @param tiltValue - Target tilt value
49
+ * @returns Matching antenna or null
50
+ */
51
+ export declare function findAntennaWithTilt(antennas: Antenna[], name: string, frequency: number | null, tiltValue: string): Antenna | null;
52
+ /**
53
+ * Get unique antenna models (by name)
54
+ * @param antennas - Array of all antennas
55
+ * @returns Array of unique antenna names
56
+ */
57
+ export declare function getUniqueAntennaModels(antennas: Antenna[]): string[];
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Antenna Data Utilities
3
+ * Helper functions for working with antenna data, tilts, and searches
4
+ */
5
+ /**
6
+ * Find an antenna by name with fallback matching strategies
7
+ * @param antennas - Array of all antennas
8
+ * @param name - Name pattern to search for
9
+ * @returns Search result with antenna and match type
10
+ */
11
+ export function findAntennaByName(antennas, name) {
12
+ if (!name || antennas.length === 0) {
13
+ return { antenna: null, matchType: 'not-found' };
14
+ }
15
+ // Strategy 1: Exact match
16
+ let found = antennas.find(a => a.name === name);
17
+ if (found) {
18
+ return { antenna: found, matchType: 'exact' };
19
+ }
20
+ // Strategy 2: Case-insensitive match
21
+ const lowerName = name.toLowerCase();
22
+ found = antennas.find(a => a.name.toLowerCase() === lowerName);
23
+ if (found) {
24
+ return { antenna: found, matchType: 'case-insensitive' };
25
+ }
26
+ // Strategy 3: Partial match (name contains search term)
27
+ found = antennas.find(a => a.name.toLowerCase().includes(lowerName));
28
+ if (found) {
29
+ return { antenna: found, matchType: 'partial' };
30
+ }
31
+ return { antenna: null, matchType: 'not-found' };
32
+ }
33
+ /**
34
+ * Collect all available electrical tilts for an antenna model
35
+ * Searches all antennas with the same name and extracts unique tilt values
36
+ * @param antennas - Array of all antennas
37
+ * @param antennaName - The antenna model name
38
+ * @param frequency - Optional frequency to filter by
39
+ * @returns Sorted array of tilt values as strings
40
+ */
41
+ export function collectAvailableTilts(antennas, antennaName, frequency) {
42
+ if (!antennaName)
43
+ return ['0'];
44
+ // Find all antennas with the same name
45
+ let sameModel = antennas.filter(a => a.name === antennaName);
46
+ // Optionally filter by frequency
47
+ if (frequency) {
48
+ sameModel = sameModel.filter(a => a.frequency === frequency);
49
+ }
50
+ // Collect all unique tilts
51
+ const allTilts = new Set();
52
+ for (const antenna of sameModel) {
53
+ if (antenna.tilt) {
54
+ const tiltStr = antenna.tilt.toString();
55
+ if (tiltStr.includes(',')) {
56
+ tiltStr.split(',').forEach(t => allTilts.add(t.trim()));
57
+ }
58
+ else {
59
+ allTilts.add(tiltStr.trim());
60
+ }
61
+ }
62
+ }
63
+ // Return sorted array (numerically)
64
+ const sorted = Array.from(allTilts).sort((a, b) => parseFloat(a) - parseFloat(b));
65
+ return sorted.length > 0 ? sorted : ['0'];
66
+ }
67
+ /**
68
+ * Collect all available frequencies for an antenna model
69
+ * @param antennas - Array of all antennas
70
+ * @param antennaName - The antenna model name
71
+ * @returns Sorted array of unique frequencies
72
+ */
73
+ export function collectAvailableFrequencies(antennas, antennaName) {
74
+ if (!antennaName)
75
+ return [];
76
+ const sameModel = antennas.filter(a => a.name === antennaName);
77
+ const frequencies = new Set();
78
+ for (const antenna of sameModel) {
79
+ if (antenna.frequency) {
80
+ frequencies.add(antenna.frequency);
81
+ }
82
+ }
83
+ return Array.from(frequencies).sort((a, b) => a - b);
84
+ }
85
+ /**
86
+ * Find the index of a tilt value in the available tilts array
87
+ * @param availableTilts - Array of available tilt values
88
+ * @param tiltValue - The tilt value to find (as number)
89
+ * @returns Index in the array, or 0 if not found
90
+ */
91
+ export function findTiltIndex(availableTilts, tiltValue) {
92
+ const index = availableTilts.findIndex(t => parseInt(t, 10) === tiltValue);
93
+ return index >= 0 ? index : 0;
94
+ }
95
+ /**
96
+ * Get the tilt value at a given index
97
+ * @param availableTilts - Array of available tilt values
98
+ * @param index - Index to retrieve
99
+ * @returns Tilt value as number
100
+ */
101
+ export function getTiltValue(availableTilts, index) {
102
+ const tilt = availableTilts[index];
103
+ return tilt ? parseInt(tilt, 10) : 0;
104
+ }
105
+ /**
106
+ * Find an antenna with specific name, frequency, and tilt
107
+ * @param antennas - Array of all antennas
108
+ * @param name - Antenna model name
109
+ * @param frequency - Target frequency (optional)
110
+ * @param tiltValue - Target tilt value
111
+ * @returns Matching antenna or null
112
+ */
113
+ export function findAntennaWithTilt(antennas, name, frequency, tiltValue) {
114
+ return antennas.find(antenna => {
115
+ if (antenna.name !== name)
116
+ return false;
117
+ if (frequency && antenna.frequency !== frequency)
118
+ return false;
119
+ if (antenna.tilt) {
120
+ const tiltStr = antenna.tilt.toString();
121
+ const antennaTilts = tiltStr.includes(',')
122
+ ? tiltStr.split(',').map(t => t.trim())
123
+ : [tiltStr.trim()];
124
+ return antennaTilts.includes(tiltValue);
125
+ }
126
+ return tiltValue === '0';
127
+ }) || null;
128
+ }
129
+ /**
130
+ * Get unique antenna models (by name)
131
+ * @param antennas - Array of all antennas
132
+ * @returns Array of unique antenna names
133
+ */
134
+ export function getUniqueAntennaModels(antennas) {
135
+ const names = new Set();
136
+ for (const antenna of antennas) {
137
+ names.add(antenna.name);
138
+ }
139
+ return Array.from(names).sort();
140
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smartnet360/svelte-components",
3
- "version": "0.0.117",
3
+ "version": "0.0.119",
4
4
  "scripts": {
5
5
  "dev": "vite dev",
6
6
  "build": "vite build && npm run prepack",