@smartnet360/svelte-components 0.0.119 → 0.0.120

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/dist/apps/antenna-tools/band-config.d.ts +53 -0
  2. package/dist/apps/antenna-tools/band-config.js +112 -0
  3. package/dist/apps/antenna-tools/components/AntennaControls.svelte +558 -0
  4. package/dist/apps/antenna-tools/components/AntennaControls.svelte.d.ts +16 -0
  5. package/dist/apps/antenna-tools/components/AntennaSettingsModal.svelte +304 -0
  6. package/dist/apps/antenna-tools/components/AntennaSettingsModal.svelte.d.ts +8 -0
  7. package/dist/apps/antenna-tools/components/AntennaTools.svelte +597 -0
  8. package/dist/apps/antenna-tools/components/AntennaTools.svelte.d.ts +42 -0
  9. package/dist/apps/antenna-tools/components/DatabaseViewer.svelte +278 -0
  10. package/dist/apps/antenna-tools/components/DatabaseViewer.svelte.d.ts +3 -0
  11. package/dist/apps/antenna-tools/components/DbNotification.svelte +67 -0
  12. package/dist/apps/antenna-tools/components/DbNotification.svelte.d.ts +18 -0
  13. package/dist/apps/antenna-tools/components/JsonImporter.svelte +115 -0
  14. package/dist/apps/antenna-tools/components/JsonImporter.svelte.d.ts +6 -0
  15. package/dist/apps/antenna-tools/components/MSIConverter.svelte +282 -0
  16. package/dist/apps/antenna-tools/components/MSIConverter.svelte.d.ts +6 -0
  17. package/dist/apps/antenna-tools/components/chart-engines/PolarAreaChart.svelte +123 -0
  18. package/dist/apps/antenna-tools/components/chart-engines/PolarAreaChart.svelte.d.ts +16 -0
  19. package/dist/apps/antenna-tools/components/chart-engines/PolarLineChart.svelte +123 -0
  20. package/dist/apps/antenna-tools/components/chart-engines/PolarLineChart.svelte.d.ts +16 -0
  21. package/dist/apps/antenna-tools/components/chart-engines/index.d.ts +9 -0
  22. package/dist/apps/antenna-tools/components/chart-engines/index.js +9 -0
  23. package/dist/apps/antenna-tools/components/index.d.ts +8 -0
  24. package/dist/apps/antenna-tools/components/index.js +10 -0
  25. package/dist/apps/antenna-tools/db.d.ts +28 -0
  26. package/dist/apps/antenna-tools/db.js +45 -0
  27. package/dist/apps/antenna-tools/index.d.ts +26 -0
  28. package/dist/apps/antenna-tools/index.js +40 -0
  29. package/dist/apps/antenna-tools/stores/antennas.d.ts +13 -0
  30. package/dist/apps/antenna-tools/stores/antennas.js +25 -0
  31. package/dist/apps/antenna-tools/stores/db-status.d.ts +32 -0
  32. package/dist/apps/antenna-tools/stores/db-status.js +38 -0
  33. package/dist/apps/antenna-tools/stores/index.d.ts +5 -0
  34. package/dist/apps/antenna-tools/stores/index.js +5 -0
  35. package/dist/apps/antenna-tools/types.d.ts +137 -0
  36. package/dist/apps/antenna-tools/types.js +16 -0
  37. package/dist/apps/antenna-tools/utils/antenna-helpers.d.ts +83 -0
  38. package/dist/apps/antenna-tools/utils/antenna-helpers.js +198 -0
  39. package/dist/apps/antenna-tools/utils/chart-engines/index.d.ts +5 -0
  40. package/dist/apps/antenna-tools/utils/chart-engines/index.js +5 -0
  41. package/dist/apps/antenna-tools/utils/chart-engines/polar-area-utils.d.ts +94 -0
  42. package/dist/apps/antenna-tools/utils/chart-engines/polar-area-utils.js +151 -0
  43. package/dist/apps/antenna-tools/utils/chart-engines/polar-line-utils.d.ts +93 -0
  44. package/dist/apps/antenna-tools/utils/chart-engines/polar-line-utils.js +139 -0
  45. package/dist/apps/antenna-tools/utils/db-utils.d.ts +50 -0
  46. package/dist/apps/antenna-tools/utils/db-utils.js +266 -0
  47. package/dist/apps/antenna-tools/utils/index.d.ts +7 -0
  48. package/dist/apps/antenna-tools/utils/index.js +7 -0
  49. package/dist/apps/antenna-tools/utils/msi-parser.d.ts +21 -0
  50. package/dist/apps/antenna-tools/utils/msi-parser.js +215 -0
  51. package/dist/apps/antenna-tools/utils/recent-antennas.d.ts +24 -0
  52. package/dist/apps/antenna-tools/utils/recent-antennas.js +64 -0
  53. package/package.json +1 -1
@@ -0,0 +1,597 @@
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, ExternalAntennaInput, ViewMode, PatternType, ChartEngineType, PatternDisplayMode } from '../types';
7
+ import AntennaControls from './AntennaControls.svelte';
8
+ import AntennaSettingsModal from './AntennaSettingsModal.svelte';
9
+ import { PolarLineChart, PolarAreaChart } from './chart-engines/index';
10
+ import { findAntennaBySpec } from '../utils/antenna-helpers';
11
+
12
+ /**
13
+ * AntennaTools - Main antenna pattern visualization component
14
+ *
15
+ * Two operating modes:
16
+ * 1. External Data Mode: Parent provides antenna specs via externalAntenna1/externalAntenna2 props
17
+ * - Antenna selection is hidden, tilts come from external data
18
+ * - Parent controls which antenna is displayed
19
+ *
20
+ * 2. Standalone Mode (default): User selects antennas from database
21
+ * - Full UI with antenna selection dropdowns
22
+ * - Settings modal for data management
23
+ */
24
+
25
+ interface Props {
26
+ // === External Data Mode Props ===
27
+ /** External antenna 1 specification (activates external mode) */
28
+ externalAntenna1?: ExternalAntennaInput | null;
29
+ /** External antenna 2 specification (for compare mode) */
30
+ externalAntenna2?: ExternalAntennaInput | null;
31
+
32
+ // === Standalone Mode Initialization Props ===
33
+ /** Initial antenna 1 name to pre-select (standalone mode only) */
34
+ initialAntenna1Name?: string;
35
+ /** Initial antenna 2 name to pre-select (standalone mode only) */
36
+ initialAntenna2Name?: string;
37
+ /** Initial electrical tilt for antenna 1 */
38
+ initialEtilt1?: number;
39
+ /** Initial electrical tilt for antenna 2 */
40
+ initialEtilt2?: number;
41
+ /** Initial mechanical tilt for antenna 1 */
42
+ initialMtilt1?: number;
43
+ /** Initial mechanical tilt for antenna 2 */
44
+ initialMtilt2?: number;
45
+
46
+ // === Common Props ===
47
+ /** Initial view mode */
48
+ initialViewMode?: ViewMode;
49
+ /** Label for antenna 1 (e.g., cell name) */
50
+ antenna1Label?: string;
51
+ /** Label for antenna 2 (e.g., cell name) */
52
+ antenna2Label?: string;
53
+ /** Hide the settings button */
54
+ hideSettings?: boolean;
55
+ }
56
+
57
+ let {
58
+ // External mode
59
+ externalAntenna1 = null,
60
+ externalAntenna2 = null,
61
+ // Standalone mode initialization
62
+ initialAntenna1Name = undefined,
63
+ initialAntenna2Name = undefined,
64
+ initialEtilt1 = 0,
65
+ initialEtilt2 = 0,
66
+ initialMtilt1 = 0,
67
+ initialMtilt2 = 0,
68
+ // Common
69
+ initialViewMode = 'single',
70
+ antenna1Label = undefined,
71
+ antenna2Label = undefined,
72
+ hideSettings = false,
73
+ }: Props = $props();
74
+
75
+ // === Derived: Operating Mode ===
76
+ let isExternalMode = $derived(externalAntenna1 !== null && externalAntenna1 !== undefined);
77
+
78
+ // === State ===
79
+ let antennas = $state<Antenna[]>([]);
80
+ let selectedAntenna = $state<Antenna | null>(null);
81
+ let selectedAntenna2 = $state<Antenna | null>(null);
82
+ let isLoading = $state(true);
83
+ let loadError = $state<string | null>(null);
84
+
85
+ // Tilt values (index for electrical, direct value for mechanical)
86
+ let ant1ElectricalTilt = $state(0);
87
+ let ant2ElectricalTilt = $state(0);
88
+ let ant1MechanicalTilt = $state(initialMtilt1);
89
+ let ant2MechanicalTilt = $state(initialMtilt2);
90
+
91
+ // Viewing mode and pattern settings
92
+ let viewMode = $state<ViewMode>(initialViewMode);
93
+ let patternType = $state<PatternType>('vertical');
94
+ let chartEngine = $state<ChartEngineType>('polar-line');
95
+ let patternDisplayMode = $state<PatternDisplayMode>('normalized');
96
+
97
+ // Settings modal state
98
+ let showSettingsModal = $state(false);
99
+
100
+ // Available tilt options (will be set from antenna data)
101
+ let availableElectricalTilts = $state<string[]>(['0', '2', '4', '6', '8']);
102
+
103
+ // === External Mode: React to external antenna changes ===
104
+ $effect(() => {
105
+ if (isExternalMode && antennas.length > 0) {
106
+ // Find and set antenna 1 from external spec
107
+ if (externalAntenna1) {
108
+ const found = findAntennaBySpec(antennas, externalAntenna1);
109
+ if (found) {
110
+ selectedAntenna = found;
111
+ // Update available tilts
112
+ updateAvailableTilts(found);
113
+ // Set tilts from external spec
114
+ ant1ElectricalTilt = findTiltIndex(availableElectricalTilts, externalAntenna1.electricalTilt ?? 0);
115
+ ant1MechanicalTilt = externalAntenna1.mechanicalTilt ?? 0;
116
+ } else {
117
+ console.warn('[AntennaTools] External antenna 1 not found in database:', externalAntenna1.name);
118
+ }
119
+ }
120
+
121
+ // Find and set antenna 2 from external spec (for compare mode)
122
+ if (externalAntenna2) {
123
+ const found2 = findAntennaBySpec(antennas, externalAntenna2);
124
+ if (found2) {
125
+ selectedAntenna2 = found2;
126
+ ant2ElectricalTilt = findTiltIndex(getAvailableTiltsForAntenna(found2), externalAntenna2.electricalTilt ?? 0);
127
+ ant2MechanicalTilt = externalAntenna2.mechanicalTilt ?? 0;
128
+ // Auto-switch to compare mode if antenna 2 is provided
129
+ if (viewMode === 'single') {
130
+ viewMode = 'compare';
131
+ }
132
+ }
133
+ }
134
+ }
135
+ });
136
+
137
+ // === Handlers ===
138
+ function handleAntenna1Change(antenna: Antenna | null) {
139
+ selectedAntenna = antenna;
140
+ if (antenna) {
141
+ updateAvailableTilts(antenna);
142
+ }
143
+ }
144
+
145
+ function handleAntenna2Change(antenna: Antenna | null) {
146
+ selectedAntenna2 = antenna;
147
+ }
148
+
149
+ function handleTilt1Change(tilt: number) {
150
+ ant1ElectricalTilt = tilt;
151
+ }
152
+
153
+ function handleTilt2Change(tilt: number) {
154
+ ant2ElectricalTilt = tilt;
155
+ }
156
+
157
+ function handleMechTilt1Change(tilt: number) {
158
+ ant1MechanicalTilt = tilt;
159
+ }
160
+
161
+ function handleMechTilt2Change(tilt: number) {
162
+ ant2MechanicalTilt = tilt;
163
+ }
164
+
165
+ function openSettings() {
166
+ showSettingsModal = true;
167
+ }
168
+
169
+ function closeSettings() {
170
+ showSettingsModal = false;
171
+ }
172
+
173
+ async function handleDataRefresh() {
174
+ try {
175
+ antennas = await loadAntennas();
176
+
177
+ // Reinitialize antenna selections after data refresh
178
+ if (antennas.length > 0 && !isExternalMode) {
179
+ selectedAntenna = antennas[0];
180
+ selectedAntenna2 = antennas.length > 1 ? antennas[1] : antennas[0];
181
+
182
+ // Reset tilt values
183
+ ant1ElectricalTilt = 0;
184
+ ant1MechanicalTilt = 0;
185
+ ant2ElectricalTilt = 0;
186
+ ant2MechanicalTilt = 0;
187
+ }
188
+ } catch (error) {
189
+ console.error('[AntennaTools] Failed to refresh data:', error);
190
+ }
191
+ }
192
+
193
+ // === Helpers ===
194
+ function updateAvailableTilts(antenna: Antenna) {
195
+ // Collect ALL available tilts from all antennas with the same name
196
+ const sameName = antennas.filter(a => a.name === antenna.name);
197
+ const allTilts = new Set<string>();
198
+ sameName.forEach(a => {
199
+ if (a.tilt) {
200
+ const tiltStr = a.tilt.toString();
201
+ if (tiltStr.includes(',')) {
202
+ tiltStr.split(',').forEach(t => allTilts.add(t.trim()));
203
+ } else {
204
+ allTilts.add(tiltStr.trim());
205
+ }
206
+ }
207
+ });
208
+ availableElectricalTilts = Array.from(allTilts).sort((a, b) => parseFloat(a) - parseFloat(b));
209
+ }
210
+
211
+ function getAvailableTiltsForAntenna(antenna: Antenna): string[] {
212
+ const sameName = antennas.filter(a => a.name === antenna.name);
213
+ const allTilts = new Set<string>();
214
+ sameName.forEach(a => {
215
+ if (a.tilt) {
216
+ const tiltStr = a.tilt.toString();
217
+ if (tiltStr.includes(',')) {
218
+ tiltStr.split(',').forEach(t => allTilts.add(t.trim()));
219
+ } else {
220
+ allTilts.add(tiltStr.trim());
221
+ }
222
+ }
223
+ });
224
+ return Array.from(allTilts).sort((a, b) => parseFloat(a) - parseFloat(b));
225
+ }
226
+
227
+ function findTiltIndex(tilts: string[], tiltValue: number): number {
228
+ const index = tilts.findIndex(t => parseInt(t, 10) === tiltValue);
229
+ return index >= 0 ? index : 0;
230
+ }
231
+
232
+ function generateChartTitle(): string {
233
+ if (viewMode === 'compare' && selectedAntenna && selectedAntenna2) {
234
+ const label1 = antenna1Label || selectedAntenna.name;
235
+ const label2 = antenna2Label || selectedAntenna2.name;
236
+ return `${label1} vs ${label2}`;
237
+ } else if (selectedAntenna) {
238
+ const label = antenna1Label || selectedAntenna.name;
239
+ return `${label} - Pattern Analysis`;
240
+ }
241
+ return 'Antenna Pattern Analysis';
242
+ }
243
+
244
+ // === Initialization ===
245
+ onMount(async () => {
246
+ try {
247
+ isLoading = true;
248
+ loadError = null;
249
+
250
+ // Load antenna data from database
251
+ antennas = await loadAntennas();
252
+
253
+ if (antennas.length === 0) {
254
+ // No antennas in database - show empty state
255
+ isLoading = false;
256
+ return;
257
+ }
258
+
259
+ // External mode: antenna selection handled by $effect
260
+ if (isExternalMode) {
261
+ isLoading = false;
262
+ return;
263
+ }
264
+
265
+ // Standalone mode: Initialize antenna selections
266
+ if (initialAntenna1Name) {
267
+ const found = findAntennaBySpec(antennas, { name: initialAntenna1Name });
268
+ selectedAntenna = found || antennas[0];
269
+ } else {
270
+ selectedAntenna = antennas[0];
271
+ }
272
+
273
+ if (selectedAntenna) {
274
+ updateAvailableTilts(selectedAntenna);
275
+ ant1ElectricalTilt = findTiltIndex(availableElectricalTilts, initialEtilt1);
276
+ }
277
+
278
+ // Set antenna 2 selection
279
+ if (initialAntenna2Name) {
280
+ const found2 = findAntennaBySpec(antennas, { name: initialAntenna2Name });
281
+ selectedAntenna2 = found2 || (antennas.length > 1 ? antennas[1] : antennas[0]);
282
+ } else {
283
+ selectedAntenna2 = antennas.length > 1 ? antennas[1] : antennas[0];
284
+ }
285
+
286
+ if (selectedAntenna2) {
287
+ const ant2Tilts = getAvailableTiltsForAntenna(selectedAntenna2);
288
+ ant2ElectricalTilt = findTiltIndex(ant2Tilts, initialEtilt2);
289
+ }
290
+
291
+ } catch (error) {
292
+ console.error('[AntennaTools] Failed to initialize:', error);
293
+ loadError = error instanceof Error ? error.message : 'Failed to load antenna data';
294
+ } finally {
295
+ isLoading = false;
296
+ }
297
+ });
298
+ </script>
299
+
300
+ <div class="container-fluid mt-4">
301
+ {#if isLoading}
302
+ <!-- Loading State -->
303
+ <div class="d-flex justify-content-center align-items-center" style="min-height: 400px;">
304
+ <div class="text-center">
305
+ <div class="spinner-border text-primary mb-3" role="status">
306
+ <span class="visually-hidden">Loading...</span>
307
+ </div>
308
+ <p class="text-muted">Loading antenna data...</p>
309
+ </div>
310
+ </div>
311
+ {:else if loadError}
312
+ <!-- Error State -->
313
+ <div class="alert alert-danger" role="alert">
314
+ <i class="bi bi-exclamation-triangle me-2"></i>
315
+ {loadError}
316
+ </div>
317
+ {:else if antennas.length === 0}
318
+ <!-- Empty State -->
319
+ <div class="text-center py-5">
320
+ <i class="bi bi-broadcast text-muted" style="font-size: 4rem;"></i>
321
+ <h4 class="mt-3">No Antenna Data</h4>
322
+ <p class="text-muted mb-4">
323
+ Import antenna patterns to get started.
324
+ </p>
325
+ {#if !hideSettings}
326
+ <button
327
+ type="button"
328
+ class="btn btn-primary btn-lg"
329
+ onclick={openSettings}
330
+ >
331
+ <i class="bi bi-database me-2"></i>
332
+ Open Database
333
+ </button>
334
+ {/if}
335
+ </div>
336
+ {:else}
337
+ <!-- Main Content -->
338
+
339
+ <!-- Controls Row -->
340
+ <div class="row mb-3">
341
+ <div class="col-md-2">
342
+ <div class="form-label">View Mode:</div>
343
+ <div class="btn-group w-100" role="group" aria-label="View mode selection">
344
+ <input
345
+ type="radio"
346
+ class="btn-check"
347
+ id="singleMode"
348
+ bind:group={viewMode}
349
+ value="single"
350
+ >
351
+ <label class="btn btn-outline-primary" for="singleMode">Single</label>
352
+
353
+ <input
354
+ type="radio"
355
+ class="btn-check"
356
+ id="compareMode"
357
+ bind:group={viewMode}
358
+ value="compare"
359
+ >
360
+ <label class="btn btn-outline-primary" for="compareMode">Compare</label>
361
+ </div>
362
+ </div>
363
+
364
+ <div class="col-md-2">
365
+ <div class="form-label">Chart Type:</div>
366
+ <div class="btn-group w-100" role="group" aria-label="Chart type selection">
367
+ <input
368
+ type="radio"
369
+ class="btn-check"
370
+ id="polarLineChart"
371
+ bind:group={chartEngine}
372
+ value="polar-line"
373
+ >
374
+ <label class="btn btn-outline-primary" for="polarLineChart">Line</label>
375
+
376
+ <input
377
+ type="radio"
378
+ class="btn-check"
379
+ id="polarAreaChart"
380
+ bind:group={chartEngine}
381
+ value="polar-area"
382
+ >
383
+ <label class="btn btn-outline-primary" for="polarAreaChart">Area</label>
384
+ </div>
385
+ </div>
386
+
387
+ <div class="col-md-3">
388
+ <div class="form-label">Display Mode:</div>
389
+ <div class="btn-group w-100" role="group" aria-label="Pattern display mode selection">
390
+ <input
391
+ type="radio"
392
+ class="btn-check"
393
+ id="normalizedMode"
394
+ bind:group={patternDisplayMode}
395
+ value="normalized"
396
+ >
397
+ <label class="btn btn-outline-primary" for="normalizedMode">Pattern</label>
398
+
399
+ <input
400
+ type="radio"
401
+ class="btn-check"
402
+ id="gainAdjustedMode"
403
+ bind:group={patternDisplayMode}
404
+ value="gain-adjusted"
405
+ >
406
+ <label class="btn btn-outline-primary" for="gainAdjustedMode">+Gain</label>
407
+ </div>
408
+ </div>
409
+
410
+ <div class="col-md-2">
411
+ <div class="form-label">Pattern Type:</div>
412
+ <div class="btn-group w-100" role="group" aria-label="Pattern type selection">
413
+ <input
414
+ type="radio"
415
+ class="btn-check"
416
+ id="horizontalPattern"
417
+ bind:group={patternType}
418
+ value="horizontal"
419
+ >
420
+ <label class="btn btn-outline-primary" for="horizontalPattern">Horizontal</label>
421
+
422
+ <input
423
+ type="radio"
424
+ class="btn-check"
425
+ id="verticalPattern"
426
+ bind:group={patternType}
427
+ value="vertical"
428
+ >
429
+ <label class="btn btn-outline-primary" for="verticalPattern">Vertical</label>
430
+ </div>
431
+ </div>
432
+
433
+ <div class="col-md-3">
434
+ {#if !hideSettings}
435
+ <div class="form-label">Actions:</div>
436
+ <button
437
+ type="button"
438
+ class="btn btn-outline-secondary w-100"
439
+ onclick={openSettings}
440
+ title="Database"
441
+ >
442
+ <i class="bi bi-database"></i> Database
443
+ </button>
444
+ {:else}
445
+ <!-- Spacer when settings hidden -->
446
+ <div></div>
447
+ {/if}
448
+ </div>
449
+ </div>
450
+
451
+ <!-- Main Layout Row -->
452
+ <div class="row">
453
+ <!-- Left Column - Antenna 1 Controls -->
454
+ <div class="col-md-3">
455
+ {#if !isExternalMode}
456
+ <AntennaControls
457
+ antennas={antennas}
458
+ selectedAntenna={selectedAntenna}
459
+ antennaNumber={1}
460
+ electricalTiltIndex={ant1ElectricalTilt}
461
+ mechanicalTilt={ant1MechanicalTilt}
462
+ colorTheme="primary"
463
+ onAntennaChange={handleAntenna1Change}
464
+ onElectricalTiltChange={handleTilt1Change}
465
+ onMechanicalTiltChange={handleMechTilt1Change}
466
+ />
467
+ {:else}
468
+ <!-- External mode: Show antenna info card -->
469
+ <div class="card border-primary">
470
+ <div class="card-header bg-primary text-white">
471
+ <i class="bi bi-broadcast me-2"></i>
472
+ {antenna1Label || 'Antenna 1'}
473
+ </div>
474
+ <div class="card-body">
475
+ {#if selectedAntenna}
476
+ <h6 class="card-title">{selectedAntenna.name}</h6>
477
+ <div class="small text-muted">
478
+ <div><strong>Band:</strong> {selectedAntenna.frequency}</div>
479
+ <div><strong>E-Tilt:</strong> {availableElectricalTilts[ant1ElectricalTilt] || 0}°</div>
480
+ <div><strong>M-Tilt:</strong> {ant1MechanicalTilt}°</div>
481
+ {#if selectedAntenna.gain_dBd}
482
+ <div><strong>Gain:</strong> {selectedAntenna.gain_dBd} dBd</div>
483
+ {/if}
484
+ </div>
485
+ {:else}
486
+ <p class="text-muted mb-0">Antenna not found in database</p>
487
+ {/if}
488
+ </div>
489
+ </div>
490
+ {/if}
491
+ </div>
492
+
493
+ <!-- Center Column - Chart -->
494
+ <div class="col-md-6">
495
+ <div class="card">
496
+ <div class="card-body p-1">
497
+ {#if chartEngine === 'polar-line'}
498
+ <PolarLineChart
499
+ {selectedAntenna}
500
+ {selectedAntenna2}
501
+ {viewMode}
502
+ {patternType}
503
+ {patternDisplayMode}
504
+ {ant1ElectricalTilt}
505
+ {ant1MechanicalTilt}
506
+ {ant2ElectricalTilt}
507
+ {ant2MechanicalTilt}
508
+ title={generateChartTitle()}
509
+ />
510
+ {:else if chartEngine === 'polar-area'}
511
+ <PolarAreaChart
512
+ {selectedAntenna}
513
+ {selectedAntenna2}
514
+ {viewMode}
515
+ {patternType}
516
+ {patternDisplayMode}
517
+ {ant1ElectricalTilt}
518
+ {ant1MechanicalTilt}
519
+ {ant2ElectricalTilt}
520
+ {ant2MechanicalTilt}
521
+ title={generateChartTitle()}
522
+ />
523
+ {/if}
524
+ </div>
525
+ </div>
526
+ </div>
527
+
528
+ <!-- Right Column - Antenna 2 Controls (only in compare mode) -->
529
+ <div class="col-md-3">
530
+ {#if viewMode === 'compare'}
531
+ {#if !isExternalMode}
532
+ <AntennaControls
533
+ antennas={antennas}
534
+ selectedAntenna={selectedAntenna2}
535
+ antennaNumber={2}
536
+ electricalTiltIndex={ant2ElectricalTilt}
537
+ mechanicalTilt={ant2MechanicalTilt}
538
+ colorTheme="warning"
539
+ onAntennaChange={handleAntenna2Change}
540
+ onElectricalTiltChange={handleTilt2Change}
541
+ onMechanicalTiltChange={handleMechTilt2Change}
542
+ />
543
+ {:else}
544
+ <!-- External mode: Show antenna 2 info card -->
545
+ <div class="card border-warning">
546
+ <div class="card-header bg-warning text-dark">
547
+ <i class="bi bi-broadcast me-2"></i>
548
+ {antenna2Label || 'Antenna 2'}
549
+ </div>
550
+ <div class="card-body">
551
+ {#if selectedAntenna2}
552
+ <h6 class="card-title">{selectedAntenna2.name}</h6>
553
+ <div class="small text-muted">
554
+ <div><strong>Band:</strong> {selectedAntenna2.frequency}</div>
555
+ <div><strong>E-Tilt:</strong> {getAvailableTiltsForAntenna(selectedAntenna2)[ant2ElectricalTilt] || 0}°</div>
556
+ <div><strong>M-Tilt:</strong> {ant2MechanicalTilt}°</div>
557
+ {#if selectedAntenna2.gain_dBd}
558
+ <div><strong>Gain:</strong> {selectedAntenna2.gain_dBd} dBd</div>
559
+ {/if}
560
+ </div>
561
+ {:else}
562
+ <p class="text-muted mb-0">Antenna not found in database</p>
563
+ {/if}
564
+ </div>
565
+ </div>
566
+ {/if}
567
+ {:else}
568
+ <!-- Empty space to keep chart centered in single mode -->
569
+ <div></div>
570
+ {/if}
571
+ </div>
572
+ </div>
573
+ {/if}
574
+ </div>
575
+
576
+ <!-- Settings Modal (only in standalone mode) -->
577
+ {#if !hideSettings}
578
+ <AntennaSettingsModal
579
+ show={showSettingsModal}
580
+ onClose={closeSettings}
581
+ onDataRefresh={handleDataRefresh}
582
+ />
583
+ {/if}
584
+
585
+ <style>
586
+ /* Chart container */
587
+ :global(.plotly .plot-container) {
588
+ border-radius: 8px !important;
589
+ }
590
+
591
+ /* Main chart card styling */
592
+ .card {
593
+ border: none;
594
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
595
+ border-radius: 12px;
596
+ }
597
+ </style>
@@ -0,0 +1,42 @@
1
+ import type { ExternalAntennaInput, ViewMode } from '../types';
2
+ /**
3
+ * AntennaTools - Main antenna pattern visualization component
4
+ *
5
+ * Two operating modes:
6
+ * 1. External Data Mode: Parent provides antenna specs via externalAntenna1/externalAntenna2 props
7
+ * - Antenna selection is hidden, tilts come from external data
8
+ * - Parent controls which antenna is displayed
9
+ *
10
+ * 2. Standalone Mode (default): User selects antennas from database
11
+ * - Full UI with antenna selection dropdowns
12
+ * - Settings modal for data management
13
+ */
14
+ interface Props {
15
+ /** External antenna 1 specification (activates external mode) */
16
+ externalAntenna1?: ExternalAntennaInput | null;
17
+ /** External antenna 2 specification (for compare mode) */
18
+ externalAntenna2?: ExternalAntennaInput | null;
19
+ /** Initial antenna 1 name to pre-select (standalone mode only) */
20
+ initialAntenna1Name?: string;
21
+ /** Initial antenna 2 name to pre-select (standalone mode only) */
22
+ initialAntenna2Name?: string;
23
+ /** Initial electrical tilt for antenna 1 */
24
+ initialEtilt1?: number;
25
+ /** Initial electrical tilt for antenna 2 */
26
+ initialEtilt2?: number;
27
+ /** Initial mechanical tilt for antenna 1 */
28
+ initialMtilt1?: number;
29
+ /** Initial mechanical tilt for antenna 2 */
30
+ initialMtilt2?: number;
31
+ /** Initial view mode */
32
+ initialViewMode?: ViewMode;
33
+ /** Label for antenna 1 (e.g., cell name) */
34
+ antenna1Label?: string;
35
+ /** Label for antenna 2 (e.g., cell name) */
36
+ antenna2Label?: string;
37
+ /** Hide the settings button */
38
+ hideSettings?: boolean;
39
+ }
40
+ declare const AntennaTools: import("svelte").Component<Props, {}, "">;
41
+ type AntennaTools = ReturnType<typeof AntennaTools>;
42
+ export default AntennaTools;