@smartnet360/svelte-components 0.0.11 → 0.0.13

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