@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,558 @@
1
+ <svelte:options runes={true} />
2
+
3
+ <script lang="ts">
4
+ import type { Antenna } from '../types';
5
+ import { sortAntennasWithRecent, addToRecentAntennas } from '../utils/recent-antennas';
6
+
7
+ interface Props {
8
+ antennas?: Antenna[];
9
+ selectedAntenna?: Antenna | null;
10
+ antennaNumber: 1 | 2;
11
+ electricalTiltIndex?: number;
12
+ mechanicalTilt?: number;
13
+ colorTheme?: 'primary' | 'warning' | 'success' | 'info' | 'secondary';
14
+ onAntennaChange?: (antenna: Antenna | null) => void;
15
+ onElectricalTiltChange?: (index: number) => void;
16
+ onMechanicalTiltChange?: (tilt: number) => void;
17
+ showStatus?: boolean;
18
+ }
19
+
20
+ let {
21
+ antennas = [],
22
+ selectedAntenna = null,
23
+ antennaNumber,
24
+ electricalTiltIndex = 0,
25
+ mechanicalTilt = 0,
26
+ colorTheme = 'primary',
27
+ onAntennaChange,
28
+ onElectricalTiltChange,
29
+ onMechanicalTiltChange,
30
+ showStatus = true
31
+ }: Props = $props();
32
+
33
+ // Internal state for slider values
34
+ let internalElectricalTiltIndex = $state(electricalTiltIndex);
35
+ let internalMechanicalTilt = $state(mechanicalTilt);
36
+ let internalSelectedAntenna = $state(selectedAntenna);
37
+ let selectedFrequency = $state<number | null>(null);
38
+
39
+ // Update internal state when props change
40
+ $effect(() => {
41
+ internalElectricalTiltIndex = electricalTiltIndex;
42
+ });
43
+
44
+ $effect(() => {
45
+ internalMechanicalTilt = mechanicalTilt;
46
+ });
47
+
48
+ $effect(() => {
49
+ // Only update internal state if the prop actually changed
50
+ // Avoid overwriting user selections
51
+ if (selectedAntenna !== internalSelectedAntenna) {
52
+ internalSelectedAntenna = selectedAntenna;
53
+ }
54
+ });
55
+
56
+ // Get available electrical tilts from antennas with the selected name and frequency
57
+ let availableElectricalTilts = $state<string[]>(['0']);
58
+ $effect(() => {
59
+ let newTilts: string[] = ['0'];
60
+
61
+ if (internalSelectedAntenna) {
62
+ // Find ALL antennas with the same name and frequency to collect all electrical tilts
63
+ let sameName = antennas.filter(a => a.name === internalSelectedAntenna!.name);
64
+
65
+ // If frequency is selected, filter by it
66
+ if (selectedFrequency) {
67
+ sameName = sameName.filter(a => a.frequency === selectedFrequency);
68
+ }
69
+
70
+ const allTilts = new Set<string>();
71
+
72
+ sameName.forEach(antenna => {
73
+ if (antenna.tilt) {
74
+ const tiltString = antenna.tilt.toString();
75
+ if (tiltString.includes(',')) {
76
+ tiltString.split(',').forEach(t => allTilts.add(t.trim()));
77
+ } else {
78
+ allTilts.add(tiltString);
79
+ }
80
+ }
81
+ });
82
+
83
+ // Convert to sorted array
84
+ newTilts = Array.from(allTilts).sort((a, b) => parseFloat(a) - parseFloat(b));
85
+ }
86
+
87
+ // Only update if actually changed to prevent infinite loops
88
+ if (JSON.stringify(availableElectricalTilts) !== JSON.stringify(newTilts)) {
89
+ availableElectricalTilts = newTilts;
90
+ }
91
+ });
92
+
93
+ // Find the specific antenna that matches name, frequency, and electrical tilt
94
+ function findAntennaWithTilt(antennaName: string, tiltIndex: number): Antenna | null {
95
+ if (!antennaName) return null;
96
+
97
+ // Handle case where availableElectricalTilts might not be populated yet
98
+ const targetTilt = availableElectricalTilts[tiltIndex] || '0';
99
+
100
+ // Find antenna with matching name, frequency, and electrical tilt
101
+ return antennas.find(antenna => {
102
+ if (antenna.name !== antennaName) return false;
103
+
104
+ // If frequency is selected, must match
105
+ if (selectedFrequency && antenna.frequency !== selectedFrequency) return false;
106
+
107
+ if (antenna.tilt) {
108
+ const tiltString = antenna.tilt.toString();
109
+ const antennasTilts = tiltString.includes(',')
110
+ ? tiltString.split(',').map(t => t.trim())
111
+ : [tiltString];
112
+
113
+ return antennasTilts.includes(targetTilt);
114
+ }
115
+
116
+ return targetTilt === '0'; // Default tilt
117
+ }) || null;
118
+ }
119
+
120
+ // Handle frequency selection
121
+ function handleFrequencyChange(frequency: number) {
122
+ selectedFrequency = frequency;
123
+
124
+ if (internalSelectedAntenna) {
125
+ // STEP 1: Calculate available tilts for the new frequency
126
+ let sameName = antennas.filter(a => a.name === internalSelectedAntenna!.name && a.frequency === frequency);
127
+
128
+ const allTilts = new Set<string>();
129
+ sameName.forEach(antenna => {
130
+ if (antenna.tilt) {
131
+ const tiltString = antenna.tilt.toString();
132
+ if (tiltString.includes(',')) {
133
+ tiltString.split(',').forEach(t => allTilts.add(t.trim()));
134
+ } else {
135
+ allTilts.add(tiltString);
136
+ }
137
+ }
138
+ });
139
+
140
+ const newAvailableTilts = Array.from(allTilts).sort((a, b) => parseFloat(a) - parseFloat(b));
141
+
142
+ // STEP 2: Determine the best tilt index to use
143
+ let bestTiltIndex = 0;
144
+ let bestTiltValue = '0';
145
+
146
+ if (newAvailableTilts.length > 0) {
147
+ // Prefer '0' tilt if available, otherwise use first available
148
+ if (newAvailableTilts.includes('0')) {
149
+ bestTiltIndex = newAvailableTilts.indexOf('0');
150
+ bestTiltValue = '0';
151
+ } else {
152
+ bestTiltIndex = 0;
153
+ bestTiltValue = newAvailableTilts[0];
154
+ }
155
+ }
156
+
157
+ // STEP 3: Find antenna with the new frequency and best tilt
158
+ const antennaWithNewFreq = antennas.find(antenna => {
159
+ if (antenna.name !== internalSelectedAntenna!.name) return false;
160
+ if (antenna.frequency !== frequency) return false;
161
+
162
+ // Check if this antenna has the target tilt
163
+ if (antenna.tilt) {
164
+ const antennaTiltString = antenna.tilt.toString();
165
+ const antennaTilts = antennaTiltString.includes(',')
166
+ ? antennaTiltString.split(',').map(t => t.trim())
167
+ : [antennaTiltString];
168
+ return antennaTilts.includes(bestTiltValue);
169
+ }
170
+ return bestTiltValue === '0';
171
+ });
172
+
173
+ if (antennaWithNewFreq) {
174
+ internalSelectedAntenna = antennaWithNewFreq;
175
+ internalElectricalTiltIndex = bestTiltIndex;
176
+
177
+ onAntennaChange?.(antennaWithNewFreq);
178
+ onElectricalTiltChange?.(bestTiltIndex);
179
+ } else {
180
+ // Reset tilt to 0 as fallback
181
+ internalElectricalTiltIndex = 0;
182
+ onElectricalTiltChange?.(0);
183
+ }
184
+ } else {
185
+ internalElectricalTiltIndex = 0;
186
+ onElectricalTiltChange?.(0);
187
+ }
188
+ }
189
+
190
+ // Handle antenna selection
191
+ function handleAntennaChange(event: Event) {
192
+ const target = event.target as HTMLSelectElement;
193
+ const antennaName = target.value;
194
+
195
+ // Reset tilt and frequency values when antenna changes
196
+ internalElectricalTiltIndex = 0;
197
+ internalMechanicalTilt = 0;
198
+ selectedFrequency = null;
199
+
200
+ // Handle "Select Antenna" case (empty string)
201
+ if (!antennaName) {
202
+ internalSelectedAntenna = null;
203
+ onAntennaChange?.(null);
204
+ onElectricalTiltChange?.(0);
205
+ onMechanicalTiltChange?.(0);
206
+ return;
207
+ }
208
+
209
+ // First, find ANY antenna with this name to get available tilts
210
+ const sampleAntenna = antennas.find(a => a.name === antennaName);
211
+ if (sampleAntenna && sampleAntenna.tilt) {
212
+ const tiltString = sampleAntenna.tilt.toString();
213
+ const tilts = tiltString.includes(',')
214
+ ? tiltString.split(',').map(t => t.trim())
215
+ : [tiltString];
216
+
217
+ // Try to find antenna with '0' tilt first, otherwise use first available
218
+ let tiltToUse = tilts.includes('0') ? '0' : tilts[0];
219
+ let tiltIndexToUse = tilts.includes('0') ? tilts.indexOf('0') : 0;
220
+
221
+ // Find the specific antenna with the chosen tilt
222
+ const newAntenna = antennas.find(antenna => {
223
+ if (antenna.name !== antennaName) return false;
224
+ if (antenna.tilt) {
225
+ const antennaTiltString = antenna.tilt.toString();
226
+ const antennaTilts = antennaTiltString.includes(',')
227
+ ? antennaTiltString.split(',').map(t => t.trim())
228
+ : [antennaTiltString];
229
+ return antennaTilts.includes(tiltToUse);
230
+ }
231
+ return tiltToUse === '0';
232
+ });
233
+
234
+ internalSelectedAntenna = newAntenna || null;
235
+ internalElectricalTiltIndex = tiltIndexToUse;
236
+
237
+ } else {
238
+ // Fallback: use findAntennaWithTilt with index 0
239
+ const newAntenna = findAntennaWithTilt(antennaName, 0);
240
+ internalSelectedAntenna = newAntenna;
241
+ }
242
+
243
+ onAntennaChange?.(internalSelectedAntenna);
244
+ onElectricalTiltChange?.(internalElectricalTiltIndex);
245
+ onMechanicalTiltChange?.(0);
246
+
247
+ // Track antenna selection in recent list
248
+ if (internalSelectedAntenna) {
249
+ addToRecentAntennas(internalSelectedAntenna);
250
+ }
251
+ }
252
+
253
+ // Handle electrical tilt changes
254
+ function handleElectricalTiltChange() {
255
+ // When tilt changes, find the antenna with the new tilt value
256
+ if (internalSelectedAntenna) {
257
+ const antennaWithNewTilt = findAntennaWithTilt(internalSelectedAntenna.name, internalElectricalTiltIndex);
258
+ if (antennaWithNewTilt) {
259
+ internalSelectedAntenna = antennaWithNewTilt;
260
+ onAntennaChange?.(antennaWithNewTilt);
261
+ }
262
+ }
263
+ onElectricalTiltChange?.(internalElectricalTiltIndex);
264
+ }
265
+
266
+ // Handle mechanical tilt changes
267
+ function handleMechanicalTiltChange() {
268
+ onMechanicalTiltChange?.(internalMechanicalTilt);
269
+ }
270
+
271
+ // Get current electrical tilt value as string
272
+ let currentElectricalTilt = $state('0');
273
+ $effect(() => {
274
+ currentElectricalTilt = availableElectricalTilts[internalElectricalTiltIndex] || '0';
275
+ });
276
+
277
+ // Get theme classes based on colorTheme prop
278
+ let themeClasses = $state({ header: '', textColor: '' });
279
+ $effect(() => {
280
+ themeClasses = {
281
+ header: getHeaderClass(colorTheme),
282
+ textColor: getTextColor(colorTheme)
283
+ };
284
+ });
285
+
286
+ function getHeaderClass(theme: 'primary' | 'warning' | 'success' | 'info' | 'secondary'): string {
287
+ const classes = {
288
+ primary: 'bg-primary text-white',
289
+ warning: 'bg-warning text-dark',
290
+ success: 'bg-success text-white',
291
+ info: 'bg-info text-white',
292
+ secondary: 'bg-secondary text-white'
293
+ };
294
+ return classes[theme];
295
+ }
296
+
297
+ function getTextColor(theme: 'primary' | 'warning' | 'success' | 'info' | 'secondary'): string {
298
+ const colors = {
299
+ primary: 'text-primary',
300
+ warning: 'text-warning',
301
+ success: 'text-success',
302
+ info: 'text-info',
303
+ secondary: 'text-secondary'
304
+ };
305
+ return colors[theme];
306
+ }
307
+
308
+ // Get unique frequency bands for the selected antenna name
309
+ let uniqueFrequencies = $state<number[]>([]);
310
+ $effect(() => {
311
+ if (antennas.length > 0 && internalSelectedAntenna) {
312
+ // Get frequencies only for the selected antenna name
313
+ const sameNameAntennas = antennas.filter(a => a.name === internalSelectedAntenna!.name);
314
+ const frequencies = [...new Set(sameNameAntennas.map(a => a.frequency))].sort((a, b) => a - b);
315
+ uniqueFrequencies = frequencies;
316
+
317
+ // Auto-select frequency if there's only one option
318
+ if (frequencies.length === 1) {
319
+ selectedFrequency = frequencies[0];
320
+ }
321
+ } else {
322
+ uniqueFrequencies = [];
323
+ selectedFrequency = null;
324
+ }
325
+ });
326
+
327
+ // Get unique antennas (by name, not by individual DB entries with different tilts)
328
+ let uniqueAntennas = $state<Antenna[]>([]);
329
+ let recentAntennas = $state<Antenna[]>([]);
330
+ let otherAntennas = $state<Antenna[]>([]);
331
+
332
+ $effect(() => {
333
+ if (antennas.length > 0) {
334
+ // Group antennas by name and take the first one from each group
335
+ const antennaMap = new Map<string, Antenna>();
336
+ antennas.forEach(antenna => {
337
+ if (!antennaMap.has(antenna.name)) {
338
+ antennaMap.set(antenna.name, antenna);
339
+ }
340
+ });
341
+ const allUniqueAntennas = Array.from(antennaMap.values()).sort((a, b) => a.name.localeCompare(b.name));
342
+
343
+ // Sort with recent antennas first
344
+ const { recent, others } = sortAntennasWithRecent(allUniqueAntennas);
345
+ recentAntennas = recent;
346
+ otherAntennas = others;
347
+ uniqueAntennas = [...recent, ...others];
348
+ }
349
+ });
350
+
351
+ // Format frequency for display (700, 800 -> "700 MHz", "800 MHz")
352
+ function formatFrequency(freq: number): string {
353
+ return `${freq} MHz`;
354
+ }
355
+ </script>
356
+
357
+ <div class="card h-100">
358
+ <div class="card-header {themeClasses.header}">
359
+ <h5 class="mb-0">Antenna {antennaNumber} Controls</h5>
360
+ </div>
361
+
362
+ <div class="card-body">
363
+ <!-- Antenna Selection -->
364
+ <div class="mb-3">
365
+ <label for="antenna{antennaNumber}Select" class="form-label">
366
+ <strong>Select Antenna:</strong>
367
+ </label>
368
+ <select
369
+ id="antenna{antennaNumber}Select"
370
+ class="form-select form-select-sm"
371
+ value={internalSelectedAntenna?.name || ''}
372
+ onchange={handleAntennaChange}
373
+ >
374
+ <option value="">-- Select Antenna --</option>
375
+
376
+ <!-- Recent Antennas -->
377
+ {#if recentAntennas.length > 0}
378
+ <optgroup label="Recent">
379
+ {#each recentAntennas as antenna}
380
+ <option value={antenna.name}>▸ {antenna.name}</option>
381
+ {/each}
382
+ </optgroup>
383
+ {/if}
384
+
385
+ <!-- All Other Antennas -->
386
+ <optgroup label="All Antennas">
387
+ {#each otherAntennas as antenna}
388
+ <option value={antenna.name}>{antenna.name}</option>
389
+ {/each}
390
+ </optgroup>
391
+ </select>
392
+ {#if internalSelectedAntenna}
393
+ <small class="text-muted mt-1 d-block">
394
+ {formatFrequency(internalSelectedAntenna.frequency)} • {internalSelectedAntenna.gain_dBd} dBd
395
+ </small>
396
+ {/if}
397
+ </div>
398
+
399
+ <!-- Frequency Band Filter -->
400
+ {#if uniqueFrequencies.length > 1}
401
+ <div class="mb-3">
402
+ <div class="form-label">
403
+ <strong>Frequency Band:</strong>
404
+ </div>
405
+ <div class="d-flex flex-wrap gap-1">
406
+ {#each uniqueFrequencies as freq}
407
+ <button
408
+ type="button"
409
+ class="btn btn-sm {selectedFrequency === freq ? 'btn-primary' : 'btn-outline-secondary'}"
410
+ onclick={() => handleFrequencyChange(freq)}
411
+ >
412
+ {formatFrequency(freq)}
413
+ </button>
414
+ {/each}
415
+ </div>
416
+ </div>
417
+ {/if}
418
+
419
+ <!-- Electrical Tilt Control -->
420
+ <div class="mb-4">
421
+ <label for="ant{antennaNumber}ETilt" class="form-label">
422
+ Electrical Tilt: <strong class={themeClasses.textColor}>{currentElectricalTilt}°</strong>
423
+ </label>
424
+ <input
425
+ type="range"
426
+ class="form-range"
427
+ id="ant{antennaNumber}ETilt"
428
+ min="0"
429
+ max={availableElectricalTilts.length - 1}
430
+ step="1"
431
+ bind:value={internalElectricalTiltIndex}
432
+ oninput={handleElectricalTiltChange}
433
+ disabled={!internalSelectedAntenna}
434
+ >
435
+ <div class="d-flex justify-content-between">
436
+ <small class="text-muted">{availableElectricalTilts[0]}°</small>
437
+ <small class="text-muted">{availableElectricalTilts[availableElectricalTilts.length - 1]}°</small>
438
+ </div>
439
+ </div>
440
+
441
+ <!-- Mechanical Tilt Control -->
442
+ <div class="mb-3">
443
+ <label for="ant{antennaNumber}MTilt" class="form-label">
444
+ Mechanical Tilt: <strong class={themeClasses.textColor}>{internalMechanicalTilt}°</strong>
445
+ </label>
446
+ <input
447
+ type="range"
448
+ class="form-range"
449
+ id="ant{antennaNumber}MTilt"
450
+ min="-10"
451
+ max="10"
452
+ step="1"
453
+ bind:value={internalMechanicalTilt}
454
+ oninput={handleMechanicalTiltChange}
455
+ disabled={!internalSelectedAntenna}
456
+ >
457
+ <div class="d-flex justify-content-between">
458
+ <small class="text-muted">-10°</small>
459
+ <small class="text-muted">+10°</small>
460
+ </div>
461
+ </div>
462
+
463
+ <!-- Status Display -->
464
+ {#if showStatus && internalSelectedAntenna}
465
+ <div class="mt-3 p-3 bg-light rounded">
466
+ <h6 class="mb-2 {themeClasses.textColor}">Current Settings</h6>
467
+ <div class="row text-sm">
468
+ <div class="col-6">
469
+ <strong>E-Tilt:</strong><br>
470
+ <span class={themeClasses.textColor}>{currentElectricalTilt}°</span>
471
+ </div>
472
+ <div class="col-6">
473
+ <strong>M-Tilt:</strong><br>
474
+ <span class={themeClasses.textColor}>{internalMechanicalTilt}°</span>
475
+ </div>
476
+ </div>
477
+ <div class="mt-2">
478
+ <small class="text-muted">
479
+ <strong>Antenna:</strong> {internalSelectedAntenna.name}<br>
480
+ <strong>Band:</strong> {formatFrequency(internalSelectedAntenna.frequency)}<br>
481
+ <strong>Gain:</strong> {internalSelectedAntenna.gain_dBd} dBd
482
+ </small>
483
+ </div>
484
+ </div>
485
+ {:else if showStatus && !internalSelectedAntenna}
486
+ <div class="mt-3 p-3 bg-light rounded text-center">
487
+ <small class="text-muted">
488
+ <strong>No antenna selected</strong><br>
489
+ Please select an antenna from the dropdown above
490
+ </small>
491
+ </div>
492
+ {/if}
493
+ </div>
494
+ </div>
495
+
496
+ <style>
497
+ /* Enhanced Bootstrap slider styling */
498
+ .form-range {
499
+ height: 8px;
500
+ }
501
+
502
+ .form-range::-webkit-slider-thumb {
503
+ height: 20px;
504
+ width: 20px;
505
+ background: linear-gradient(145deg, #ffffff, #f8f9fa);
506
+ border: 2px solid #0d6efd;
507
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
508
+ transition: all 0.2s ease;
509
+ }
510
+
511
+ .form-range::-webkit-slider-thumb:hover {
512
+ transform: scale(1.1);
513
+ box-shadow: 0 4px 12px rgba(13, 110, 253, 0.3);
514
+ }
515
+
516
+ .form-range::-moz-range-thumb {
517
+ height: 20px;
518
+ width: 20px;
519
+ background: linear-gradient(145deg, #ffffff, #f8f9fa);
520
+ border: 2px solid #0d6efd;
521
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
522
+ transition: all 0.2s ease;
523
+ border-radius: 50%;
524
+ }
525
+
526
+ .form-range::-moz-range-thumb:hover {
527
+ transform: scale(1.1);
528
+ box-shadow: 0 4px 12px rgba(13, 110, 253, 0.3);
529
+ }
530
+
531
+ /* Card styling */
532
+ .card {
533
+ border: none;
534
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
535
+ border-radius: 12px;
536
+ transition: transform 0.2s ease, box-shadow 0.2s ease;
537
+ }
538
+
539
+ .card:hover {
540
+ transform: translateY(-2px);
541
+ box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15);
542
+ }
543
+
544
+ .card-header {
545
+ border-radius: 12px 12px 0 0 !important;
546
+ border-bottom: none;
547
+ }
548
+
549
+ .text-sm {
550
+ font-size: 0.875rem;
551
+ }
552
+
553
+ /* Status panel styling */
554
+ .bg-light {
555
+ background-color: rgba(248, 249, 250, 0.8) !important;
556
+ border: 1px solid rgba(0, 0, 0, 0.05);
557
+ }
558
+ </style>
@@ -0,0 +1,16 @@
1
+ import type { Antenna } from '../types';
2
+ interface Props {
3
+ antennas?: Antenna[];
4
+ selectedAntenna?: Antenna | null;
5
+ antennaNumber: 1 | 2;
6
+ electricalTiltIndex?: number;
7
+ mechanicalTilt?: number;
8
+ colorTheme?: 'primary' | 'warning' | 'success' | 'info' | 'secondary';
9
+ onAntennaChange?: (antenna: Antenna | null) => void;
10
+ onElectricalTiltChange?: (index: number) => void;
11
+ onMechanicalTiltChange?: (tilt: number) => void;
12
+ showStatus?: boolean;
13
+ }
14
+ declare const AntennaControls: import("svelte").Component<Props, {}, "">;
15
+ type AntennaControls = ReturnType<typeof AntennaControls>;
16
+ export default AntennaControls;