@smartnet360/svelte-components 0.0.11 → 0.0.12

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 (88) hide show
  1. package/dist/apps/antenna-pattern/components/AntennaControls.svelte +424 -0
  2. package/dist/apps/antenna-pattern/components/AntennaControls.svelte.d.ts +16 -0
  3. package/dist/apps/antenna-pattern/components/AntennaDataDropdown.svelte +62 -0
  4. package/dist/apps/antenna-pattern/components/AntennaDataDropdown.svelte.d.ts +18 -0
  5. package/dist/apps/antenna-pattern/components/AntennaDiagrams.svelte +339 -0
  6. package/dist/apps/antenna-pattern/components/AntennaDiagrams.svelte.d.ts +3 -0
  7. package/dist/apps/antenna-pattern/components/AntennaSettingsModal.svelte +299 -0
  8. package/dist/apps/antenna-pattern/components/AntennaSettingsModal.svelte.d.ts +24 -0
  9. package/dist/apps/antenna-pattern/components/DbNotification.svelte +67 -0
  10. package/dist/apps/antenna-pattern/components/DbNotification.svelte.d.ts +18 -0
  11. package/dist/apps/antenna-pattern/components/JsonImporter.svelte +116 -0
  12. package/dist/apps/antenna-pattern/components/JsonImporter.svelte.d.ts +18 -0
  13. package/dist/apps/antenna-pattern/components/MSIConverter.svelte +209 -0
  14. package/dist/apps/antenna-pattern/components/MSIConverter.svelte.d.ts +18 -0
  15. package/dist/apps/antenna-pattern/components/PlotlyRadarChart.svelte +252 -0
  16. package/dist/apps/antenna-pattern/components/PlotlyRadarChart.svelte.d.ts +22 -0
  17. package/dist/apps/antenna-pattern/db.d.ts +24 -0
  18. package/dist/apps/antenna-pattern/db.js +15 -0
  19. package/dist/apps/antenna-pattern/helpers/plotly-utils.d.ts +54 -0
  20. package/dist/apps/antenna-pattern/helpers/plotly-utils.js +324 -0
  21. package/dist/apps/antenna-pattern/index.d.ts +15 -0
  22. package/dist/apps/antenna-pattern/index.js +19 -0
  23. package/dist/apps/antenna-pattern/stores/antennas.d.ts +5 -0
  24. package/dist/apps/antenna-pattern/stores/antennas.js +14 -0
  25. package/dist/apps/antenna-pattern/stores/db-status.d.ts +28 -0
  26. package/dist/apps/antenna-pattern/stores/db-status.js +34 -0
  27. package/dist/apps/antenna-pattern/utils/db-utils.d.ts +9 -0
  28. package/dist/apps/antenna-pattern/utils/db-utils.js +180 -0
  29. package/dist/apps/antenna-pattern/utils/init-db.d.ts +2 -0
  30. package/dist/apps/antenna-pattern/utils/init-db.js +95 -0
  31. package/dist/apps/antenna-pattern/utils/msi-parser.d.ts +3 -0
  32. package/dist/apps/antenna-pattern/utils/msi-parser.js +197 -0
  33. package/dist/apps/antenna-pattern/utils/plotly-chart-utils.d.ts +101 -0
  34. package/dist/apps/antenna-pattern/utils/plotly-chart-utils.js +152 -0
  35. package/dist/apps/index.d.ts +1 -0
  36. package/dist/apps/index.js +6 -0
  37. package/dist/{Charts → core/Charts}/ChartComponent.svelte +131 -39
  38. package/dist/{Charts → core/Charts}/charts.model.d.ts +1 -1
  39. package/dist/{Desktop → core/Desktop}/GridRenderer.svelte +1 -1
  40. package/dist/core/index.d.ts +2 -0
  41. package/dist/core/index.js +6 -0
  42. package/dist/index.d.ts +2 -2
  43. package/dist/index.js +6 -2
  44. package/package.json +6 -2
  45. /package/dist/{Charts → core/Charts}/ChartCard.svelte +0 -0
  46. /package/dist/{Charts → core/Charts}/ChartCard.svelte.d.ts +0 -0
  47. /package/dist/{Charts → core/Charts}/ChartComponent.svelte.d.ts +0 -0
  48. /package/dist/{Charts → core/Charts}/adapt.d.ts +0 -0
  49. /package/dist/{Charts → core/Charts}/adapt.js +0 -0
  50. /package/dist/{Charts → core/Charts}/charts.model.js +0 -0
  51. /package/dist/{Charts → core/Charts}/data-utils.d.ts +0 -0
  52. /package/dist/{Charts → core/Charts}/data-utils.js +0 -0
  53. /package/dist/{Charts → core/Charts}/index.d.ts +0 -0
  54. /package/dist/{Charts → core/Charts}/index.js +0 -0
  55. /package/dist/{Charts → core/Charts}/plotly.d.ts +0 -0
  56. /package/dist/{Desktop → core/Desktop}/Desktop.svelte +0 -0
  57. /package/dist/{Desktop → core/Desktop}/Desktop.svelte.d.ts +0 -0
  58. /package/dist/{Desktop → core/Desktop}/Grid/Half.svelte +0 -0
  59. /package/dist/{Desktop → core/Desktop}/Grid/Half.svelte.d.ts +0 -0
  60. /package/dist/{Desktop → core/Desktop}/Grid/Quarter.svelte +0 -0
  61. /package/dist/{Desktop → core/Desktop}/Grid/Quarter.svelte.d.ts +0 -0
  62. /package/dist/{Desktop → core/Desktop}/Grid/ResizeHandle.svelte +0 -0
  63. /package/dist/{Desktop → core/Desktop}/Grid/ResizeHandle.svelte.d.ts +0 -0
  64. /package/dist/{Desktop → core/Desktop}/Grid/index.d.ts +0 -0
  65. /package/dist/{Desktop → core/Desktop}/Grid/index.js +0 -0
  66. /package/dist/{Desktop → core/Desktop}/Grid/resizeStore.d.ts +0 -0
  67. /package/dist/{Desktop → core/Desktop}/Grid/resizeStore.js +0 -0
  68. /package/dist/{Desktop → core/Desktop}/GridRenderer.svelte.d.ts +0 -0
  69. /package/dist/{Desktop → core/Desktop}/GridSelector/ComponentPalette.svelte +0 -0
  70. /package/dist/{Desktop → core/Desktop}/GridSelector/ComponentPalette.svelte.d.ts +0 -0
  71. /package/dist/{Desktop → core/Desktop}/GridSelector/ConfigurationPanel.svelte +0 -0
  72. /package/dist/{Desktop → core/Desktop}/GridSelector/ConfigurationPanel.svelte.d.ts +0 -0
  73. /package/dist/{Desktop → core/Desktop}/GridSelector/GridSelector.svelte +0 -0
  74. /package/dist/{Desktop → core/Desktop}/GridSelector/GridSelector.svelte.d.ts +0 -0
  75. /package/dist/{Desktop → core/Desktop}/GridSelector/LayoutPicker.svelte +0 -0
  76. /package/dist/{Desktop → core/Desktop}/GridSelector/LayoutPicker.svelte.d.ts +0 -0
  77. /package/dist/{Desktop → core/Desktop}/GridSelector/LayoutPreview.svelte +0 -0
  78. /package/dist/{Desktop → core/Desktop}/GridSelector/LayoutPreview.svelte.d.ts +0 -0
  79. /package/dist/{Desktop → core/Desktop}/GridSelector/index.d.ts +0 -0
  80. /package/dist/{Desktop → core/Desktop}/GridSelector/index.js +0 -0
  81. /package/dist/{Desktop → core/Desktop}/GridViewer.svelte +0 -0
  82. /package/dist/{Desktop → core/Desktop}/GridViewer.svelte.d.ts +0 -0
  83. /package/dist/{Desktop → core/Desktop}/gridLayouts.d.ts +0 -0
  84. /package/dist/{Desktop → core/Desktop}/gridLayouts.js +0 -0
  85. /package/dist/{Desktop → core/Desktop}/index.d.ts +0 -0
  86. /package/dist/{Desktop → core/Desktop}/index.js +0 -0
  87. /package/dist/{Desktop → core/Desktop}/launchHelpers.d.ts +0 -0
  88. /package/dist/{Desktop → core/Desktop}/launchHelpers.js +0 -0
@@ -0,0 +1,424 @@
1
+ <svelte:options runes={true} />
2
+
3
+ <script lang="ts">
4
+ import type { Antenna } from '../db';
5
+
6
+ interface Props {
7
+ antennas?: Antenna[];
8
+ selectedAntenna?: Antenna | null;
9
+ antennaNumber: 1 | 2;
10
+ electricalTiltIndex?: number;
11
+ mechanicalTilt?: number;
12
+ colorTheme?: 'primary' | 'warning' | 'success' | 'info' | 'secondary';
13
+ onAntennaChange?: (antenna: Antenna | null) => void;
14
+ onElectricalTiltChange?: (index: number) => void;
15
+ onMechanicalTiltChange?: (tilt: number) => void;
16
+ showStatus?: boolean;
17
+ }
18
+
19
+ let {
20
+ antennas = [],
21
+ selectedAntenna = null,
22
+ antennaNumber,
23
+ electricalTiltIndex = 0,
24
+ mechanicalTilt = 0,
25
+ colorTheme = 'primary',
26
+ onAntennaChange,
27
+ onElectricalTiltChange,
28
+ onMechanicalTiltChange,
29
+ showStatus = true
30
+ }: Props = $props();
31
+
32
+ // Internal state for slider values
33
+ let internalElectricalTiltIndex = $state(electricalTiltIndex);
34
+ let internalMechanicalTilt = $state(mechanicalTilt);
35
+ let internalSelectedAntenna = $state(selectedAntenna);
36
+ let selectedFrequency = $state<number | null>(null);
37
+
38
+ // Update internal state when props change
39
+ $effect(() => {
40
+ internalElectricalTiltIndex = electricalTiltIndex;
41
+ });
42
+
43
+ $effect(() => {
44
+ internalMechanicalTilt = mechanicalTilt;
45
+ });
46
+
47
+ $effect(() => {
48
+ internalSelectedAntenna = selectedAntenna;
49
+ });
50
+
51
+ // Get available electrical tilts from antennas with the selected name and frequency
52
+ let availableElectricalTilts = $state<string[]>(['0']);
53
+ $effect(() => {
54
+ if (internalSelectedAntenna) {
55
+ // Find ALL antennas with the same name and frequency to collect all electrical tilts
56
+ let sameName = antennas.filter(a => a.name === internalSelectedAntenna!.name);
57
+
58
+ // If frequency is selected, filter by it
59
+ if (selectedFrequency) {
60
+ sameName = sameName.filter(a => a.frequency === selectedFrequency);
61
+ }
62
+
63
+ const allTilts = new Set<string>();
64
+
65
+ sameName.forEach(antenna => {
66
+ if (antenna.tilt) {
67
+ const tiltString = antenna.tilt.toString();
68
+ if (tiltString.includes(',')) {
69
+ tiltString.split(',').forEach(t => allTilts.add(t.trim()));
70
+ } else {
71
+ allTilts.add(tiltString);
72
+ }
73
+ }
74
+ });
75
+
76
+ // Convert to sorted array
77
+ availableElectricalTilts = Array.from(allTilts).sort((a, b) => parseFloat(a) - parseFloat(b));
78
+ } else {
79
+ availableElectricalTilts = ['0'];
80
+ }
81
+ });
82
+
83
+ // Find the specific antenna that matches name, frequency, and electrical tilt
84
+ function findAntennaWithTilt(antennaName: string, tiltIndex: number): Antenna | null {
85
+ if (!antennaName || !availableElectricalTilts[tiltIndex]) return null;
86
+
87
+ const targetTilt = availableElectricalTilts[tiltIndex];
88
+
89
+ // Find antenna with matching name, frequency, and electrical tilt
90
+ return antennas.find(antenna => {
91
+ if (antenna.name !== antennaName) return false;
92
+
93
+ // If frequency is selected, must match
94
+ if (selectedFrequency && antenna.frequency !== selectedFrequency) return false;
95
+
96
+ if (antenna.tilt) {
97
+ const tiltString = antenna.tilt.toString();
98
+ const antennasTilts = tiltString.includes(',')
99
+ ? tiltString.split(',').map(t => t.trim())
100
+ : [tiltString];
101
+
102
+ return antennasTilts.includes(targetTilt);
103
+ }
104
+
105
+ return targetTilt === '0'; // Default tilt
106
+ }) || null;
107
+ }
108
+
109
+ // Handle frequency selection
110
+ function handleFrequencyChange(frequency: number) {
111
+ selectedFrequency = frequency;
112
+
113
+ // Reset tilt when frequency changes
114
+ internalElectricalTiltIndex = 0;
115
+
116
+ // Find the antenna with the new frequency and default tilt
117
+ if (internalSelectedAntenna) {
118
+ const antennaWithNewFreq = findAntennaWithTilt(internalSelectedAntenna.name, 0);
119
+ if (antennaWithNewFreq) {
120
+ internalSelectedAntenna = antennaWithNewFreq;
121
+ onAntennaChange?.(antennaWithNewFreq);
122
+ }
123
+ }
124
+ onElectricalTiltChange?.(0);
125
+ }
126
+
127
+ // Handle antenna selection
128
+ function handleAntennaChange(event: Event) {
129
+ const target = event.target as HTMLSelectElement;
130
+ const antennaName = target.value;
131
+
132
+ // Reset tilt and frequency values when antenna changes
133
+ internalElectricalTiltIndex = 0;
134
+ internalMechanicalTilt = 0;
135
+ selectedFrequency = null;
136
+
137
+ // Find the specific antenna with the default tilt (index 0)
138
+ const newAntenna = findAntennaWithTilt(antennaName, 0);
139
+ internalSelectedAntenna = newAntenna;
140
+
141
+ onAntennaChange?.(newAntenna);
142
+ onElectricalTiltChange?.(0);
143
+ onMechanicalTiltChange?.(0);
144
+ }
145
+
146
+ // Handle electrical tilt changes
147
+ function handleElectricalTiltChange() {
148
+ // When tilt changes, find the antenna with the new tilt value
149
+ if (internalSelectedAntenna) {
150
+ const antennaWithNewTilt = findAntennaWithTilt(internalSelectedAntenna.name, internalElectricalTiltIndex);
151
+ if (antennaWithNewTilt) {
152
+ internalSelectedAntenna = antennaWithNewTilt;
153
+ onAntennaChange?.(antennaWithNewTilt);
154
+ }
155
+ }
156
+ onElectricalTiltChange?.(internalElectricalTiltIndex);
157
+ }
158
+
159
+ // Handle mechanical tilt changes
160
+ function handleMechanicalTiltChange() {
161
+ onMechanicalTiltChange?.(internalMechanicalTilt);
162
+ }
163
+
164
+ // Get current electrical tilt value as string
165
+ let currentElectricalTilt = $state('0');
166
+ $effect(() => {
167
+ currentElectricalTilt = availableElectricalTilts[internalElectricalTiltIndex] || '0';
168
+ });
169
+
170
+ // Get theme classes based on colorTheme prop
171
+ let themeClasses = $state({ header: '', textColor: '' });
172
+ $effect(() => {
173
+ themeClasses = {
174
+ header: getHeaderClass(colorTheme),
175
+ textColor: getTextColor(colorTheme)
176
+ };
177
+ });
178
+
179
+ function getHeaderClass(theme: 'primary' | 'warning' | 'success' | 'info' | 'secondary'): string {
180
+ const classes = {
181
+ primary: 'bg-primary text-white',
182
+ warning: 'bg-warning text-dark',
183
+ success: 'bg-success text-white',
184
+ info: 'bg-info text-white',
185
+ secondary: 'bg-secondary text-white'
186
+ };
187
+ return classes[theme];
188
+ }
189
+
190
+ function getTextColor(theme: 'primary' | 'warning' | 'success' | 'info' | 'secondary'): string {
191
+ const colors = {
192
+ primary: 'text-primary',
193
+ warning: 'text-warning',
194
+ success: 'text-success',
195
+ info: 'text-info',
196
+ secondary: 'text-secondary'
197
+ };
198
+ return colors[theme];
199
+ }
200
+
201
+ // Get unique frequency bands for the selected antenna name
202
+ let uniqueFrequencies = $state<number[]>([]);
203
+ $effect(() => {
204
+ if (antennas.length > 0 && internalSelectedAntenna) {
205
+ // Get frequencies only for the selected antenna name
206
+ const sameNameAntennas = antennas.filter(a => a.name === internalSelectedAntenna!.name);
207
+ const frequencies = [...new Set(sameNameAntennas.map(a => a.frequency))].sort((a, b) => a - b);
208
+ uniqueFrequencies = frequencies;
209
+
210
+ // Auto-select frequency if there's only one option
211
+ if (frequencies.length === 1) {
212
+ selectedFrequency = frequencies[0];
213
+ }
214
+ } else {
215
+ uniqueFrequencies = [];
216
+ selectedFrequency = null;
217
+ }
218
+ });
219
+
220
+ // Get unique antennas (by name, not by individual DB entries with different tilts)
221
+ let uniqueAntennas = $state<Antenna[]>([]);
222
+ $effect(() => {
223
+ if (antennas.length > 0) {
224
+ // Group antennas by name and take the first one from each group
225
+ const antennaMap = new Map<string, Antenna>();
226
+ antennas.forEach(antenna => {
227
+ if (!antennaMap.has(antenna.name)) {
228
+ antennaMap.set(antenna.name, antenna);
229
+ }
230
+ });
231
+ uniqueAntennas = Array.from(antennaMap.values()).sort((a, b) => a.name.localeCompare(b.name));
232
+ }
233
+ });
234
+ </script>
235
+
236
+ <div class="card h-100">
237
+ <div class="card-header {themeClasses.header}">
238
+ <h5 class="mb-0">Antenna {antennaNumber} Controls</h5>
239
+ </div>
240
+
241
+ <div class="card-body">
242
+ <!-- Antenna Selection -->
243
+ <div class="mb-3">
244
+ <label for="antenna{antennaNumber}Select" class="form-label">
245
+ <strong>Select Antenna:</strong>
246
+ </label>
247
+ <select
248
+ id="antenna{antennaNumber}Select"
249
+ class="form-select form-select-sm"
250
+ value={internalSelectedAntenna?.name || ''}
251
+ onchange={handleAntennaChange}
252
+ >
253
+ <option value="">-- Select Antenna --</option>
254
+ {#each uniqueAntennas as antenna}
255
+ <option value={antenna.name}>{antenna.name}</option>
256
+ {/each}
257
+ </select>
258
+ {#if internalSelectedAntenna}
259
+ <small class="text-muted mt-1 d-block">
260
+ {internalSelectedAntenna.frequency} MHz • {internalSelectedAntenna.gain_dBd} dBd
261
+ </small>
262
+ {/if}
263
+ </div>
264
+
265
+ <!-- Frequency Band Filter -->
266
+ {#if uniqueFrequencies.length > 1}
267
+ <div class="mb-3">
268
+ <div class="form-label">
269
+ <strong>Frequency Filter:</strong>
270
+ </div>
271
+ <div class="d-flex flex-wrap gap-1">
272
+ {#each uniqueFrequencies as freq}
273
+ <button
274
+ type="button"
275
+ class="btn btn-sm {selectedFrequency === freq ? 'btn-primary' : 'btn-outline-secondary'}"
276
+ onclick={() => handleFrequencyChange(freq)}
277
+ >
278
+ {freq} MHz
279
+ </button>
280
+ {/each}
281
+ </div>
282
+ </div>
283
+ {/if}
284
+
285
+ <!-- Electrical Tilt Control -->
286
+ <div class="mb-4">
287
+ <label for="ant{antennaNumber}ETilt" class="form-label">
288
+ Electrical Tilt: <strong class={themeClasses.textColor}>{currentElectricalTilt}°</strong>
289
+ </label>
290
+ <input
291
+ type="range"
292
+ class="form-range"
293
+ id="ant{antennaNumber}ETilt"
294
+ min="0"
295
+ max={availableElectricalTilts.length - 1}
296
+ step="1"
297
+ bind:value={internalElectricalTiltIndex}
298
+ oninput={handleElectricalTiltChange}
299
+ disabled={!internalSelectedAntenna}
300
+ >
301
+ <div class="d-flex justify-content-between">
302
+ <small class="text-muted">{availableElectricalTilts[0]}°</small>
303
+ <small class="text-muted">{availableElectricalTilts[availableElectricalTilts.length - 1]}°</small>
304
+ </div>
305
+ </div>
306
+
307
+ <!-- Mechanical Tilt Control -->
308
+ <div class="mb-3">
309
+ <label for="ant{antennaNumber}MTilt" class="form-label">
310
+ Mechanical Tilt: <strong class={themeClasses.textColor}>{internalMechanicalTilt}°</strong>
311
+ </label>
312
+ <input
313
+ type="range"
314
+ class="form-range"
315
+ id="ant{antennaNumber}MTilt"
316
+ min="-10"
317
+ max="10"
318
+ step="1"
319
+ bind:value={internalMechanicalTilt}
320
+ oninput={handleMechanicalTiltChange}
321
+ disabled={!internalSelectedAntenna}
322
+ >
323
+ <div class="d-flex justify-content-between">
324
+ <small class="text-muted">-10°</small>
325
+ <small class="text-muted">+10°</small>
326
+ </div>
327
+ </div>
328
+
329
+ <!-- Status Display -->
330
+ {#if showStatus && internalSelectedAntenna}
331
+ <div class="mt-3 p-3 bg-light rounded">
332
+ <h6 class="mb-2 {themeClasses.textColor}">Current Settings</h6>
333
+ <div class="row text-sm">
334
+ <div class="col-6">
335
+ <strong>E-Tilt:</strong><br>
336
+ <span class={themeClasses.textColor}>{currentElectricalTilt}°</span>
337
+ </div>
338
+ <div class="col-6">
339
+ <strong>M-Tilt:</strong><br>
340
+ <span class={themeClasses.textColor}>{internalMechanicalTilt}°</span>
341
+ </div>
342
+ </div>
343
+ <div class="mt-2">
344
+ <small class="text-muted">
345
+ <strong>Antenna:</strong> {internalSelectedAntenna.name}<br>
346
+ <strong>Frequency:</strong> {internalSelectedAntenna.frequency} MHz<br>
347
+ <strong>Gain:</strong> {internalSelectedAntenna.gain_dBd} dBd
348
+ </small>
349
+ </div>
350
+ </div>
351
+ {:else if showStatus && !internalSelectedAntenna}
352
+ <div class="mt-3 p-3 bg-light rounded text-center">
353
+ <small class="text-muted">
354
+ <strong>No antenna selected</strong><br>
355
+ Please select an antenna from the dropdown above
356
+ </small>
357
+ </div>
358
+ {/if}
359
+ </div>
360
+ </div>
361
+
362
+ <style>
363
+ /* Enhanced Bootstrap slider styling */
364
+ .form-range {
365
+ height: 8px;
366
+ }
367
+
368
+ .form-range::-webkit-slider-thumb {
369
+ height: 20px;
370
+ width: 20px;
371
+ background: linear-gradient(145deg, #ffffff, #f8f9fa);
372
+ border: 2px solid #0d6efd;
373
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
374
+ transition: all 0.2s ease;
375
+ }
376
+
377
+ .form-range::-webkit-slider-thumb:hover {
378
+ transform: scale(1.1);
379
+ box-shadow: 0 4px 12px rgba(13, 110, 253, 0.3);
380
+ }
381
+
382
+ .form-range::-moz-range-thumb {
383
+ height: 20px;
384
+ width: 20px;
385
+ background: linear-gradient(145deg, #ffffff, #f8f9fa);
386
+ border: 2px solid #0d6efd;
387
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
388
+ transition: all 0.2s ease;
389
+ border-radius: 50%;
390
+ }
391
+
392
+ .form-range::-moz-range-thumb:hover {
393
+ transform: scale(1.1);
394
+ box-shadow: 0 4px 12px rgba(13, 110, 253, 0.3);
395
+ }
396
+
397
+ /* Card styling */
398
+ .card {
399
+ border: none;
400
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
401
+ border-radius: 12px;
402
+ transition: transform 0.2s ease, box-shadow 0.2s ease;
403
+ }
404
+
405
+ .card:hover {
406
+ transform: translateY(-2px);
407
+ box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15);
408
+ }
409
+
410
+ .card-header {
411
+ border-radius: 12px 12px 0 0 !important;
412
+ border-bottom: none;
413
+ }
414
+
415
+ .text-sm {
416
+ font-size: 0.875rem;
417
+ }
418
+
419
+ /* Status panel styling */
420
+ .bg-light {
421
+ background-color: rgba(248, 249, 250, 0.8) !important;
422
+ border: 1px solid rgba(0, 0, 0, 0.05);
423
+ }
424
+ </style>
@@ -0,0 +1,16 @@
1
+ import type { Antenna } from '../db';
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;
@@ -0,0 +1,62 @@
1
+ <script lang="ts">
2
+ import { page } from '$app/stores';
3
+ import { base } from '$app/paths';
4
+ import { onMount } from 'svelte';
5
+ import { browser } from '$app/environment';
6
+
7
+ let dropdown: HTMLLIElement | null = null;
8
+ let isOpen = false;
9
+
10
+ // Toggle the dropdown manually
11
+ function toggleDropdown() {
12
+ isOpen = !isOpen;
13
+ }
14
+
15
+ // Close dropdown when clicking outside
16
+ function handleClickOutside(event: MouseEvent) {
17
+ const target = event.target as Node | null;
18
+ if (dropdown && target && !dropdown.contains(target) && isOpen) {
19
+ isOpen = false;
20
+ }
21
+ }
22
+
23
+ onMount(() => {
24
+ if (browser) {
25
+ document.addEventListener('click', handleClickOutside);
26
+ }
27
+
28
+ return () => {
29
+ if (browser) {
30
+ document.removeEventListener('click', handleClickOutside);
31
+ }
32
+ };
33
+ });
34
+
35
+ // For active state styling
36
+ $: isActive = $page.url.pathname.endsWith('/import-msi') ||
37
+ $page.url.pathname.endsWith('/import') ||
38
+ $page.url.pathname.endsWith('/download') ||
39
+ $page.url.pathname.endsWith('/clear') ||
40
+ $page.url.pathname.endsWith('/convert');
41
+ </script>
42
+
43
+ <li class="nav-item dropdown {isOpen ? 'show' : ''}" bind:this={dropdown}>
44
+ <button
45
+ type="button"
46
+ class="nav-link dropdown-toggle {isActive ? 'active' : ''}"
47
+ id="antennaDataDropdown"
48
+ on:click={toggleDropdown}
49
+ aria-expanded={isOpen}
50
+ >
51
+ Antenna Data
52
+ </button>
53
+ <ul class="dropdown-menu {isOpen ? 'show' : ''}" aria-labelledby="antennaDataDropdown">
54
+ <li><a class="dropdown-item {$page.url.pathname.endsWith('/import-msi') ? 'active' : ''}" href="{base}/apps/antenna-patterns/import-msi">Import MSI Files</a></li>
55
+ <li><a class="dropdown-item {$page.url.pathname.endsWith('/convert') ? 'active' : ''}" href="{base}/apps/antenna-patterns/convert">Convert MSI</a></li>
56
+ <li><hr class="dropdown-divider"></li>
57
+ <li><a class="dropdown-item {$page.url.pathname.endsWith('/import') ? 'active' : ''}" href="{base}/apps/antenna-patterns/import">Import JSON</a></li>
58
+ <li><a class="dropdown-item {$page.url.pathname.endsWith('/download') ? 'active' : ''}" href="{base}/apps/antenna-patterns/download">Download JSON</a></li>
59
+ <li><hr class="dropdown-divider"></li>
60
+ <li><a class="dropdown-item {$page.url.pathname.endsWith('/clear') ? 'active' : ''}" href="{base}/apps/antenna-patterns/clear">Clear Database</a></li>
61
+ </ul>
62
+ </li>
@@ -0,0 +1,18 @@
1
+ interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
2
+ new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
3
+ $$bindings?: Bindings;
4
+ } & Exports;
5
+ (internal: unknown, props: {
6
+ $$events?: Events;
7
+ $$slots?: Slots;
8
+ }): Exports & {
9
+ $set?: any;
10
+ $on?: any;
11
+ };
12
+ z_$$bindings?: Bindings;
13
+ }
14
+ declare const AntennaDataDropdown: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
15
+ [evt: string]: CustomEvent<any>;
16
+ }, {}, {}, string>;
17
+ type AntennaDataDropdown = InstanceType<typeof AntennaDataDropdown>;
18
+ export default AntennaDataDropdown;