@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.
- package/dist/apps/antenna-tools/band-config.d.ts +53 -0
- package/dist/apps/antenna-tools/band-config.js +112 -0
- package/dist/apps/antenna-tools/components/AntennaControls.svelte +558 -0
- package/dist/apps/antenna-tools/components/AntennaControls.svelte.d.ts +16 -0
- package/dist/apps/antenna-tools/components/AntennaSettingsModal.svelte +304 -0
- package/dist/apps/antenna-tools/components/AntennaSettingsModal.svelte.d.ts +8 -0
- package/dist/apps/antenna-tools/components/AntennaTools.svelte +597 -0
- package/dist/apps/antenna-tools/components/AntennaTools.svelte.d.ts +42 -0
- package/dist/apps/antenna-tools/components/DatabaseViewer.svelte +278 -0
- package/dist/apps/antenna-tools/components/DatabaseViewer.svelte.d.ts +3 -0
- package/dist/apps/antenna-tools/components/DbNotification.svelte +67 -0
- package/dist/apps/antenna-tools/components/DbNotification.svelte.d.ts +18 -0
- package/dist/apps/antenna-tools/components/JsonImporter.svelte +115 -0
- package/dist/apps/antenna-tools/components/JsonImporter.svelte.d.ts +6 -0
- package/dist/apps/antenna-tools/components/MSIConverter.svelte +282 -0
- package/dist/apps/antenna-tools/components/MSIConverter.svelte.d.ts +6 -0
- package/dist/apps/antenna-tools/components/chart-engines/PolarAreaChart.svelte +123 -0
- package/dist/apps/antenna-tools/components/chart-engines/PolarAreaChart.svelte.d.ts +16 -0
- package/dist/apps/antenna-tools/components/chart-engines/PolarLineChart.svelte +123 -0
- package/dist/apps/antenna-tools/components/chart-engines/PolarLineChart.svelte.d.ts +16 -0
- package/dist/apps/antenna-tools/components/chart-engines/index.d.ts +9 -0
- package/dist/apps/antenna-tools/components/chart-engines/index.js +9 -0
- package/dist/apps/antenna-tools/components/index.d.ts +8 -0
- package/dist/apps/antenna-tools/components/index.js +10 -0
- package/dist/apps/antenna-tools/db.d.ts +28 -0
- package/dist/apps/antenna-tools/db.js +45 -0
- package/dist/apps/antenna-tools/index.d.ts +26 -0
- package/dist/apps/antenna-tools/index.js +40 -0
- package/dist/apps/antenna-tools/stores/antennas.d.ts +13 -0
- package/dist/apps/antenna-tools/stores/antennas.js +25 -0
- package/dist/apps/antenna-tools/stores/db-status.d.ts +32 -0
- package/dist/apps/antenna-tools/stores/db-status.js +38 -0
- package/dist/apps/antenna-tools/stores/index.d.ts +5 -0
- package/dist/apps/antenna-tools/stores/index.js +5 -0
- package/dist/apps/antenna-tools/types.d.ts +137 -0
- package/dist/apps/antenna-tools/types.js +16 -0
- package/dist/apps/antenna-tools/utils/antenna-helpers.d.ts +83 -0
- package/dist/apps/antenna-tools/utils/antenna-helpers.js +198 -0
- package/dist/apps/antenna-tools/utils/chart-engines/index.d.ts +5 -0
- package/dist/apps/antenna-tools/utils/chart-engines/index.js +5 -0
- package/dist/apps/antenna-tools/utils/chart-engines/polar-area-utils.d.ts +94 -0
- package/dist/apps/antenna-tools/utils/chart-engines/polar-area-utils.js +151 -0
- package/dist/apps/antenna-tools/utils/chart-engines/polar-line-utils.d.ts +93 -0
- package/dist/apps/antenna-tools/utils/chart-engines/polar-line-utils.js +139 -0
- package/dist/apps/antenna-tools/utils/db-utils.d.ts +50 -0
- package/dist/apps/antenna-tools/utils/db-utils.js +266 -0
- package/dist/apps/antenna-tools/utils/index.d.ts +7 -0
- package/dist/apps/antenna-tools/utils/index.js +7 -0
- package/dist/apps/antenna-tools/utils/msi-parser.d.ts +21 -0
- package/dist/apps/antenna-tools/utils/msi-parser.js +215 -0
- package/dist/apps/antenna-tools/utils/recent-antennas.d.ts +24 -0
- package/dist/apps/antenna-tools/utils/recent-antennas.js +64 -0
- 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;
|