@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.
- package/dist/apps/antenna-pattern/components/AntennaControls.svelte +506 -0
- package/dist/apps/antenna-pattern/components/AntennaControls.svelte.d.ts +16 -0
- package/dist/apps/antenna-pattern/components/AntennaDataDropdown.svelte +62 -0
- package/dist/apps/antenna-pattern/components/AntennaDataDropdown.svelte.d.ts +18 -0
- package/dist/apps/antenna-pattern/components/AntennaDiagrams.svelte +339 -0
- package/dist/apps/antenna-pattern/components/AntennaDiagrams.svelte.d.ts +3 -0
- package/dist/apps/antenna-pattern/components/AntennaSettingsModal.svelte +299 -0
- package/dist/apps/antenna-pattern/components/AntennaSettingsModal.svelte.d.ts +24 -0
- package/dist/apps/antenna-pattern/components/DbNotification.svelte +67 -0
- package/dist/apps/antenna-pattern/components/DbNotification.svelte.d.ts +18 -0
- package/dist/apps/antenna-pattern/components/JsonImporter.svelte +116 -0
- package/dist/apps/antenna-pattern/components/JsonImporter.svelte.d.ts +18 -0
- package/dist/apps/antenna-pattern/components/MSIConverter.svelte +207 -0
- package/dist/apps/antenna-pattern/components/MSIConverter.svelte.d.ts +18 -0
- package/dist/apps/antenna-pattern/components/PlotlyRadarChart.svelte +252 -0
- package/dist/apps/antenna-pattern/components/PlotlyRadarChart.svelte.d.ts +22 -0
- package/dist/apps/antenna-pattern/db.d.ts +24 -0
- package/dist/apps/antenna-pattern/db.js +15 -0
- package/dist/apps/antenna-pattern/helpers/plotly-utils.d.ts +54 -0
- package/dist/apps/antenna-pattern/helpers/plotly-utils.js +324 -0
- package/dist/apps/antenna-pattern/index.d.ts +15 -0
- package/dist/apps/antenna-pattern/index.js +19 -0
- package/dist/apps/antenna-pattern/stores/antennas.d.ts +5 -0
- package/dist/apps/antenna-pattern/stores/antennas.js +14 -0
- package/dist/apps/antenna-pattern/stores/db-status.d.ts +28 -0
- package/dist/apps/antenna-pattern/stores/db-status.js +34 -0
- package/dist/apps/antenna-pattern/utils/db-utils.d.ts +9 -0
- package/dist/apps/antenna-pattern/utils/db-utils.js +180 -0
- package/dist/apps/antenna-pattern/utils/init-db.d.ts +2 -0
- package/dist/apps/antenna-pattern/utils/init-db.js +95 -0
- package/dist/apps/antenna-pattern/utils/msi-parser.d.ts +3 -0
- package/dist/apps/antenna-pattern/utils/msi-parser.js +197 -0
- package/dist/apps/antenna-pattern/utils/plotly-chart-utils.d.ts +101 -0
- package/dist/apps/antenna-pattern/utils/plotly-chart-utils.js +152 -0
- package/dist/apps/index.d.ts +1 -0
- package/dist/apps/index.js +6 -0
- package/dist/{Charts → core/Charts}/ChartComponent.svelte +131 -39
- package/dist/{Charts → core/Charts}/charts.model.d.ts +1 -1
- package/dist/{Desktop → core/Desktop}/GridRenderer.svelte +1 -1
- package/dist/core/index.d.ts +2 -0
- package/dist/core/index.js +6 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +6 -2
- package/package.json +6 -2
- /package/dist/{Charts → core/Charts}/ChartCard.svelte +0 -0
- /package/dist/{Charts → core/Charts}/ChartCard.svelte.d.ts +0 -0
- /package/dist/{Charts → core/Charts}/ChartComponent.svelte.d.ts +0 -0
- /package/dist/{Charts → core/Charts}/adapt.d.ts +0 -0
- /package/dist/{Charts → core/Charts}/adapt.js +0 -0
- /package/dist/{Charts → core/Charts}/charts.model.js +0 -0
- /package/dist/{Charts → core/Charts}/data-utils.d.ts +0 -0
- /package/dist/{Charts → core/Charts}/data-utils.js +0 -0
- /package/dist/{Charts → core/Charts}/index.d.ts +0 -0
- /package/dist/{Charts → core/Charts}/index.js +0 -0
- /package/dist/{Charts → core/Charts}/plotly.d.ts +0 -0
- /package/dist/{Desktop → core/Desktop}/Desktop.svelte +0 -0
- /package/dist/{Desktop → core/Desktop}/Desktop.svelte.d.ts +0 -0
- /package/dist/{Desktop → core/Desktop}/Grid/Half.svelte +0 -0
- /package/dist/{Desktop → core/Desktop}/Grid/Half.svelte.d.ts +0 -0
- /package/dist/{Desktop → core/Desktop}/Grid/Quarter.svelte +0 -0
- /package/dist/{Desktop → core/Desktop}/Grid/Quarter.svelte.d.ts +0 -0
- /package/dist/{Desktop → core/Desktop}/Grid/ResizeHandle.svelte +0 -0
- /package/dist/{Desktop → core/Desktop}/Grid/ResizeHandle.svelte.d.ts +0 -0
- /package/dist/{Desktop → core/Desktop}/Grid/index.d.ts +0 -0
- /package/dist/{Desktop → core/Desktop}/Grid/index.js +0 -0
- /package/dist/{Desktop → core/Desktop}/Grid/resizeStore.d.ts +0 -0
- /package/dist/{Desktop → core/Desktop}/Grid/resizeStore.js +0 -0
- /package/dist/{Desktop → core/Desktop}/GridRenderer.svelte.d.ts +0 -0
- /package/dist/{Desktop → core/Desktop}/GridSelector/ComponentPalette.svelte +0 -0
- /package/dist/{Desktop → core/Desktop}/GridSelector/ComponentPalette.svelte.d.ts +0 -0
- /package/dist/{Desktop → core/Desktop}/GridSelector/ConfigurationPanel.svelte +0 -0
- /package/dist/{Desktop → core/Desktop}/GridSelector/ConfigurationPanel.svelte.d.ts +0 -0
- /package/dist/{Desktop → core/Desktop}/GridSelector/GridSelector.svelte +0 -0
- /package/dist/{Desktop → core/Desktop}/GridSelector/GridSelector.svelte.d.ts +0 -0
- /package/dist/{Desktop → core/Desktop}/GridSelector/LayoutPicker.svelte +0 -0
- /package/dist/{Desktop → core/Desktop}/GridSelector/LayoutPicker.svelte.d.ts +0 -0
- /package/dist/{Desktop → core/Desktop}/GridSelector/LayoutPreview.svelte +0 -0
- /package/dist/{Desktop → core/Desktop}/GridSelector/LayoutPreview.svelte.d.ts +0 -0
- /package/dist/{Desktop → core/Desktop}/GridSelector/index.d.ts +0 -0
- /package/dist/{Desktop → core/Desktop}/GridSelector/index.js +0 -0
- /package/dist/{Desktop → core/Desktop}/GridViewer.svelte +0 -0
- /package/dist/{Desktop → core/Desktop}/GridViewer.svelte.d.ts +0 -0
- /package/dist/{Desktop → core/Desktop}/gridLayouts.d.ts +0 -0
- /package/dist/{Desktop → core/Desktop}/gridLayouts.js +0 -0
- /package/dist/{Desktop → core/Desktop}/index.d.ts +0 -0
- /package/dist/{Desktop → core/Desktop}/index.js +0 -0
- /package/dist/{Desktop → core/Desktop}/launchHelpers.d.ts +0 -0
- /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;
|