@smartnet360/svelte-components 0.0.10 → 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.
- package/dist/apps/antenna-pattern/components/AntennaControls.svelte +424 -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 +209 -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/{Charts → core/Charts}/data-utils.js +0 -4
- 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}/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,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;
|