@smartnet360/svelte-components 0.0.118 → 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.
@@ -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.118",
3
+ "version": "0.0.119",
4
4
  "scripts": {
5
5
  "dev": "vite dev",
6
6
  "build": "vite build && npm run prepack",