@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,597 @@
|
|
|
1
|
+
<svelte:options runes={true} />
|
|
2
|
+
|
|
3
|
+
<script lang="ts">
|
|
4
|
+
import { onMount } from 'svelte';
|
|
5
|
+
import { loadAntennas } from '../utils/db-utils';
|
|
6
|
+
import type { Antenna, ExternalAntennaInput, ViewMode, PatternType, ChartEngineType, PatternDisplayMode } from '../types';
|
|
7
|
+
import AntennaControls from './AntennaControls.svelte';
|
|
8
|
+
import AntennaSettingsModal from './AntennaSettingsModal.svelte';
|
|
9
|
+
import { PolarLineChart, PolarAreaChart } from './chart-engines/index';
|
|
10
|
+
import { findAntennaBySpec } from '../utils/antenna-helpers';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* AntennaTools - Main antenna pattern visualization component
|
|
14
|
+
*
|
|
15
|
+
* Two operating modes:
|
|
16
|
+
* 1. External Data Mode: Parent provides antenna specs via externalAntenna1/externalAntenna2 props
|
|
17
|
+
* - Antenna selection is hidden, tilts come from external data
|
|
18
|
+
* - Parent controls which antenna is displayed
|
|
19
|
+
*
|
|
20
|
+
* 2. Standalone Mode (default): User selects antennas from database
|
|
21
|
+
* - Full UI with antenna selection dropdowns
|
|
22
|
+
* - Settings modal for data management
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
interface Props {
|
|
26
|
+
// === External Data Mode Props ===
|
|
27
|
+
/** External antenna 1 specification (activates external mode) */
|
|
28
|
+
externalAntenna1?: ExternalAntennaInput | null;
|
|
29
|
+
/** External antenna 2 specification (for compare mode) */
|
|
30
|
+
externalAntenna2?: ExternalAntennaInput | null;
|
|
31
|
+
|
|
32
|
+
// === Standalone Mode Initialization Props ===
|
|
33
|
+
/** Initial antenna 1 name to pre-select (standalone mode only) */
|
|
34
|
+
initialAntenna1Name?: string;
|
|
35
|
+
/** Initial antenna 2 name to pre-select (standalone mode only) */
|
|
36
|
+
initialAntenna2Name?: string;
|
|
37
|
+
/** Initial electrical tilt for antenna 1 */
|
|
38
|
+
initialEtilt1?: number;
|
|
39
|
+
/** Initial electrical tilt for antenna 2 */
|
|
40
|
+
initialEtilt2?: number;
|
|
41
|
+
/** Initial mechanical tilt for antenna 1 */
|
|
42
|
+
initialMtilt1?: number;
|
|
43
|
+
/** Initial mechanical tilt for antenna 2 */
|
|
44
|
+
initialMtilt2?: number;
|
|
45
|
+
|
|
46
|
+
// === Common Props ===
|
|
47
|
+
/** Initial view mode */
|
|
48
|
+
initialViewMode?: ViewMode;
|
|
49
|
+
/** Label for antenna 1 (e.g., cell name) */
|
|
50
|
+
antenna1Label?: string;
|
|
51
|
+
/** Label for antenna 2 (e.g., cell name) */
|
|
52
|
+
antenna2Label?: string;
|
|
53
|
+
/** Hide the settings button */
|
|
54
|
+
hideSettings?: boolean;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
let {
|
|
58
|
+
// External mode
|
|
59
|
+
externalAntenna1 = null,
|
|
60
|
+
externalAntenna2 = null,
|
|
61
|
+
// Standalone mode initialization
|
|
62
|
+
initialAntenna1Name = undefined,
|
|
63
|
+
initialAntenna2Name = undefined,
|
|
64
|
+
initialEtilt1 = 0,
|
|
65
|
+
initialEtilt2 = 0,
|
|
66
|
+
initialMtilt1 = 0,
|
|
67
|
+
initialMtilt2 = 0,
|
|
68
|
+
// Common
|
|
69
|
+
initialViewMode = 'single',
|
|
70
|
+
antenna1Label = undefined,
|
|
71
|
+
antenna2Label = undefined,
|
|
72
|
+
hideSettings = false,
|
|
73
|
+
}: Props = $props();
|
|
74
|
+
|
|
75
|
+
// === Derived: Operating Mode ===
|
|
76
|
+
let isExternalMode = $derived(externalAntenna1 !== null && externalAntenna1 !== undefined);
|
|
77
|
+
|
|
78
|
+
// === State ===
|
|
79
|
+
let antennas = $state<Antenna[]>([]);
|
|
80
|
+
let selectedAntenna = $state<Antenna | null>(null);
|
|
81
|
+
let selectedAntenna2 = $state<Antenna | null>(null);
|
|
82
|
+
let isLoading = $state(true);
|
|
83
|
+
let loadError = $state<string | null>(null);
|
|
84
|
+
|
|
85
|
+
// Tilt values (index for electrical, direct value for mechanical)
|
|
86
|
+
let ant1ElectricalTilt = $state(0);
|
|
87
|
+
let ant2ElectricalTilt = $state(0);
|
|
88
|
+
let ant1MechanicalTilt = $state(initialMtilt1);
|
|
89
|
+
let ant2MechanicalTilt = $state(initialMtilt2);
|
|
90
|
+
|
|
91
|
+
// Viewing mode and pattern settings
|
|
92
|
+
let viewMode = $state<ViewMode>(initialViewMode);
|
|
93
|
+
let patternType = $state<PatternType>('vertical');
|
|
94
|
+
let chartEngine = $state<ChartEngineType>('polar-line');
|
|
95
|
+
let patternDisplayMode = $state<PatternDisplayMode>('normalized');
|
|
96
|
+
|
|
97
|
+
// Settings modal state
|
|
98
|
+
let showSettingsModal = $state(false);
|
|
99
|
+
|
|
100
|
+
// Available tilt options (will be set from antenna data)
|
|
101
|
+
let availableElectricalTilts = $state<string[]>(['0', '2', '4', '6', '8']);
|
|
102
|
+
|
|
103
|
+
// === External Mode: React to external antenna changes ===
|
|
104
|
+
$effect(() => {
|
|
105
|
+
if (isExternalMode && antennas.length > 0) {
|
|
106
|
+
// Find and set antenna 1 from external spec
|
|
107
|
+
if (externalAntenna1) {
|
|
108
|
+
const found = findAntennaBySpec(antennas, externalAntenna1);
|
|
109
|
+
if (found) {
|
|
110
|
+
selectedAntenna = found;
|
|
111
|
+
// Update available tilts
|
|
112
|
+
updateAvailableTilts(found);
|
|
113
|
+
// Set tilts from external spec
|
|
114
|
+
ant1ElectricalTilt = findTiltIndex(availableElectricalTilts, externalAntenna1.electricalTilt ?? 0);
|
|
115
|
+
ant1MechanicalTilt = externalAntenna1.mechanicalTilt ?? 0;
|
|
116
|
+
} else {
|
|
117
|
+
console.warn('[AntennaTools] External antenna 1 not found in database:', externalAntenna1.name);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Find and set antenna 2 from external spec (for compare mode)
|
|
122
|
+
if (externalAntenna2) {
|
|
123
|
+
const found2 = findAntennaBySpec(antennas, externalAntenna2);
|
|
124
|
+
if (found2) {
|
|
125
|
+
selectedAntenna2 = found2;
|
|
126
|
+
ant2ElectricalTilt = findTiltIndex(getAvailableTiltsForAntenna(found2), externalAntenna2.electricalTilt ?? 0);
|
|
127
|
+
ant2MechanicalTilt = externalAntenna2.mechanicalTilt ?? 0;
|
|
128
|
+
// Auto-switch to compare mode if antenna 2 is provided
|
|
129
|
+
if (viewMode === 'single') {
|
|
130
|
+
viewMode = 'compare';
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// === Handlers ===
|
|
138
|
+
function handleAntenna1Change(antenna: Antenna | null) {
|
|
139
|
+
selectedAntenna = antenna;
|
|
140
|
+
if (antenna) {
|
|
141
|
+
updateAvailableTilts(antenna);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function handleAntenna2Change(antenna: Antenna | null) {
|
|
146
|
+
selectedAntenna2 = antenna;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function handleTilt1Change(tilt: number) {
|
|
150
|
+
ant1ElectricalTilt = tilt;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function handleTilt2Change(tilt: number) {
|
|
154
|
+
ant2ElectricalTilt = tilt;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function handleMechTilt1Change(tilt: number) {
|
|
158
|
+
ant1MechanicalTilt = tilt;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function handleMechTilt2Change(tilt: number) {
|
|
162
|
+
ant2MechanicalTilt = tilt;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function openSettings() {
|
|
166
|
+
showSettingsModal = true;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function closeSettings() {
|
|
170
|
+
showSettingsModal = false;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
async function handleDataRefresh() {
|
|
174
|
+
try {
|
|
175
|
+
antennas = await loadAntennas();
|
|
176
|
+
|
|
177
|
+
// Reinitialize antenna selections after data refresh
|
|
178
|
+
if (antennas.length > 0 && !isExternalMode) {
|
|
179
|
+
selectedAntenna = antennas[0];
|
|
180
|
+
selectedAntenna2 = antennas.length > 1 ? antennas[1] : antennas[0];
|
|
181
|
+
|
|
182
|
+
// Reset tilt values
|
|
183
|
+
ant1ElectricalTilt = 0;
|
|
184
|
+
ant1MechanicalTilt = 0;
|
|
185
|
+
ant2ElectricalTilt = 0;
|
|
186
|
+
ant2MechanicalTilt = 0;
|
|
187
|
+
}
|
|
188
|
+
} catch (error) {
|
|
189
|
+
console.error('[AntennaTools] Failed to refresh data:', error);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// === Helpers ===
|
|
194
|
+
function updateAvailableTilts(antenna: Antenna) {
|
|
195
|
+
// Collect ALL available tilts from all antennas with the same name
|
|
196
|
+
const sameName = antennas.filter(a => a.name === antenna.name);
|
|
197
|
+
const allTilts = new Set<string>();
|
|
198
|
+
sameName.forEach(a => {
|
|
199
|
+
if (a.tilt) {
|
|
200
|
+
const tiltStr = a.tilt.toString();
|
|
201
|
+
if (tiltStr.includes(',')) {
|
|
202
|
+
tiltStr.split(',').forEach(t => allTilts.add(t.trim()));
|
|
203
|
+
} else {
|
|
204
|
+
allTilts.add(tiltStr.trim());
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
availableElectricalTilts = Array.from(allTilts).sort((a, b) => parseFloat(a) - parseFloat(b));
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function getAvailableTiltsForAntenna(antenna: Antenna): string[] {
|
|
212
|
+
const sameName = antennas.filter(a => a.name === antenna.name);
|
|
213
|
+
const allTilts = new Set<string>();
|
|
214
|
+
sameName.forEach(a => {
|
|
215
|
+
if (a.tilt) {
|
|
216
|
+
const tiltStr = a.tilt.toString();
|
|
217
|
+
if (tiltStr.includes(',')) {
|
|
218
|
+
tiltStr.split(',').forEach(t => allTilts.add(t.trim()));
|
|
219
|
+
} else {
|
|
220
|
+
allTilts.add(tiltStr.trim());
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
return Array.from(allTilts).sort((a, b) => parseFloat(a) - parseFloat(b));
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function findTiltIndex(tilts: string[], tiltValue: number): number {
|
|
228
|
+
const index = tilts.findIndex(t => parseInt(t, 10) === tiltValue);
|
|
229
|
+
return index >= 0 ? index : 0;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function generateChartTitle(): string {
|
|
233
|
+
if (viewMode === 'compare' && selectedAntenna && selectedAntenna2) {
|
|
234
|
+
const label1 = antenna1Label || selectedAntenna.name;
|
|
235
|
+
const label2 = antenna2Label || selectedAntenna2.name;
|
|
236
|
+
return `${label1} vs ${label2}`;
|
|
237
|
+
} else if (selectedAntenna) {
|
|
238
|
+
const label = antenna1Label || selectedAntenna.name;
|
|
239
|
+
return `${label} - Pattern Analysis`;
|
|
240
|
+
}
|
|
241
|
+
return 'Antenna Pattern Analysis';
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// === Initialization ===
|
|
245
|
+
onMount(async () => {
|
|
246
|
+
try {
|
|
247
|
+
isLoading = true;
|
|
248
|
+
loadError = null;
|
|
249
|
+
|
|
250
|
+
// Load antenna data from database
|
|
251
|
+
antennas = await loadAntennas();
|
|
252
|
+
|
|
253
|
+
if (antennas.length === 0) {
|
|
254
|
+
// No antennas in database - show empty state
|
|
255
|
+
isLoading = false;
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// External mode: antenna selection handled by $effect
|
|
260
|
+
if (isExternalMode) {
|
|
261
|
+
isLoading = false;
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Standalone mode: Initialize antenna selections
|
|
266
|
+
if (initialAntenna1Name) {
|
|
267
|
+
const found = findAntennaBySpec(antennas, { name: initialAntenna1Name });
|
|
268
|
+
selectedAntenna = found || antennas[0];
|
|
269
|
+
} else {
|
|
270
|
+
selectedAntenna = antennas[0];
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (selectedAntenna) {
|
|
274
|
+
updateAvailableTilts(selectedAntenna);
|
|
275
|
+
ant1ElectricalTilt = findTiltIndex(availableElectricalTilts, initialEtilt1);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Set antenna 2 selection
|
|
279
|
+
if (initialAntenna2Name) {
|
|
280
|
+
const found2 = findAntennaBySpec(antennas, { name: initialAntenna2Name });
|
|
281
|
+
selectedAntenna2 = found2 || (antennas.length > 1 ? antennas[1] : antennas[0]);
|
|
282
|
+
} else {
|
|
283
|
+
selectedAntenna2 = antennas.length > 1 ? antennas[1] : antennas[0];
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if (selectedAntenna2) {
|
|
287
|
+
const ant2Tilts = getAvailableTiltsForAntenna(selectedAntenna2);
|
|
288
|
+
ant2ElectricalTilt = findTiltIndex(ant2Tilts, initialEtilt2);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
} catch (error) {
|
|
292
|
+
console.error('[AntennaTools] Failed to initialize:', error);
|
|
293
|
+
loadError = error instanceof Error ? error.message : 'Failed to load antenna data';
|
|
294
|
+
} finally {
|
|
295
|
+
isLoading = false;
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
</script>
|
|
299
|
+
|
|
300
|
+
<div class="container-fluid mt-4">
|
|
301
|
+
{#if isLoading}
|
|
302
|
+
<!-- Loading State -->
|
|
303
|
+
<div class="d-flex justify-content-center align-items-center" style="min-height: 400px;">
|
|
304
|
+
<div class="text-center">
|
|
305
|
+
<div class="spinner-border text-primary mb-3" role="status">
|
|
306
|
+
<span class="visually-hidden">Loading...</span>
|
|
307
|
+
</div>
|
|
308
|
+
<p class="text-muted">Loading antenna data...</p>
|
|
309
|
+
</div>
|
|
310
|
+
</div>
|
|
311
|
+
{:else if loadError}
|
|
312
|
+
<!-- Error State -->
|
|
313
|
+
<div class="alert alert-danger" role="alert">
|
|
314
|
+
<i class="bi bi-exclamation-triangle me-2"></i>
|
|
315
|
+
{loadError}
|
|
316
|
+
</div>
|
|
317
|
+
{:else if antennas.length === 0}
|
|
318
|
+
<!-- Empty State -->
|
|
319
|
+
<div class="text-center py-5">
|
|
320
|
+
<i class="bi bi-broadcast text-muted" style="font-size: 4rem;"></i>
|
|
321
|
+
<h4 class="mt-3">No Antenna Data</h4>
|
|
322
|
+
<p class="text-muted mb-4">
|
|
323
|
+
Import antenna patterns to get started.
|
|
324
|
+
</p>
|
|
325
|
+
{#if !hideSettings}
|
|
326
|
+
<button
|
|
327
|
+
type="button"
|
|
328
|
+
class="btn btn-primary btn-lg"
|
|
329
|
+
onclick={openSettings}
|
|
330
|
+
>
|
|
331
|
+
<i class="bi bi-database me-2"></i>
|
|
332
|
+
Open Database
|
|
333
|
+
</button>
|
|
334
|
+
{/if}
|
|
335
|
+
</div>
|
|
336
|
+
{:else}
|
|
337
|
+
<!-- Main Content -->
|
|
338
|
+
|
|
339
|
+
<!-- Controls Row -->
|
|
340
|
+
<div class="row mb-3">
|
|
341
|
+
<div class="col-md-2">
|
|
342
|
+
<div class="form-label">View Mode:</div>
|
|
343
|
+
<div class="btn-group w-100" role="group" aria-label="View mode selection">
|
|
344
|
+
<input
|
|
345
|
+
type="radio"
|
|
346
|
+
class="btn-check"
|
|
347
|
+
id="singleMode"
|
|
348
|
+
bind:group={viewMode}
|
|
349
|
+
value="single"
|
|
350
|
+
>
|
|
351
|
+
<label class="btn btn-outline-primary" for="singleMode">Single</label>
|
|
352
|
+
|
|
353
|
+
<input
|
|
354
|
+
type="radio"
|
|
355
|
+
class="btn-check"
|
|
356
|
+
id="compareMode"
|
|
357
|
+
bind:group={viewMode}
|
|
358
|
+
value="compare"
|
|
359
|
+
>
|
|
360
|
+
<label class="btn btn-outline-primary" for="compareMode">Compare</label>
|
|
361
|
+
</div>
|
|
362
|
+
</div>
|
|
363
|
+
|
|
364
|
+
<div class="col-md-2">
|
|
365
|
+
<div class="form-label">Chart Type:</div>
|
|
366
|
+
<div class="btn-group w-100" role="group" aria-label="Chart type selection">
|
|
367
|
+
<input
|
|
368
|
+
type="radio"
|
|
369
|
+
class="btn-check"
|
|
370
|
+
id="polarLineChart"
|
|
371
|
+
bind:group={chartEngine}
|
|
372
|
+
value="polar-line"
|
|
373
|
+
>
|
|
374
|
+
<label class="btn btn-outline-primary" for="polarLineChart">Line</label>
|
|
375
|
+
|
|
376
|
+
<input
|
|
377
|
+
type="radio"
|
|
378
|
+
class="btn-check"
|
|
379
|
+
id="polarAreaChart"
|
|
380
|
+
bind:group={chartEngine}
|
|
381
|
+
value="polar-area"
|
|
382
|
+
>
|
|
383
|
+
<label class="btn btn-outline-primary" for="polarAreaChart">Area</label>
|
|
384
|
+
</div>
|
|
385
|
+
</div>
|
|
386
|
+
|
|
387
|
+
<div class="col-md-3">
|
|
388
|
+
<div class="form-label">Display Mode:</div>
|
|
389
|
+
<div class="btn-group w-100" role="group" aria-label="Pattern display mode selection">
|
|
390
|
+
<input
|
|
391
|
+
type="radio"
|
|
392
|
+
class="btn-check"
|
|
393
|
+
id="normalizedMode"
|
|
394
|
+
bind:group={patternDisplayMode}
|
|
395
|
+
value="normalized"
|
|
396
|
+
>
|
|
397
|
+
<label class="btn btn-outline-primary" for="normalizedMode">Pattern</label>
|
|
398
|
+
|
|
399
|
+
<input
|
|
400
|
+
type="radio"
|
|
401
|
+
class="btn-check"
|
|
402
|
+
id="gainAdjustedMode"
|
|
403
|
+
bind:group={patternDisplayMode}
|
|
404
|
+
value="gain-adjusted"
|
|
405
|
+
>
|
|
406
|
+
<label class="btn btn-outline-primary" for="gainAdjustedMode">+Gain</label>
|
|
407
|
+
</div>
|
|
408
|
+
</div>
|
|
409
|
+
|
|
410
|
+
<div class="col-md-2">
|
|
411
|
+
<div class="form-label">Pattern Type:</div>
|
|
412
|
+
<div class="btn-group w-100" role="group" aria-label="Pattern type selection">
|
|
413
|
+
<input
|
|
414
|
+
type="radio"
|
|
415
|
+
class="btn-check"
|
|
416
|
+
id="horizontalPattern"
|
|
417
|
+
bind:group={patternType}
|
|
418
|
+
value="horizontal"
|
|
419
|
+
>
|
|
420
|
+
<label class="btn btn-outline-primary" for="horizontalPattern">Horizontal</label>
|
|
421
|
+
|
|
422
|
+
<input
|
|
423
|
+
type="radio"
|
|
424
|
+
class="btn-check"
|
|
425
|
+
id="verticalPattern"
|
|
426
|
+
bind:group={patternType}
|
|
427
|
+
value="vertical"
|
|
428
|
+
>
|
|
429
|
+
<label class="btn btn-outline-primary" for="verticalPattern">Vertical</label>
|
|
430
|
+
</div>
|
|
431
|
+
</div>
|
|
432
|
+
|
|
433
|
+
<div class="col-md-3">
|
|
434
|
+
{#if !hideSettings}
|
|
435
|
+
<div class="form-label">Actions:</div>
|
|
436
|
+
<button
|
|
437
|
+
type="button"
|
|
438
|
+
class="btn btn-outline-secondary w-100"
|
|
439
|
+
onclick={openSettings}
|
|
440
|
+
title="Database"
|
|
441
|
+
>
|
|
442
|
+
<i class="bi bi-database"></i> Database
|
|
443
|
+
</button>
|
|
444
|
+
{:else}
|
|
445
|
+
<!-- Spacer when settings hidden -->
|
|
446
|
+
<div></div>
|
|
447
|
+
{/if}
|
|
448
|
+
</div>
|
|
449
|
+
</div>
|
|
450
|
+
|
|
451
|
+
<!-- Main Layout Row -->
|
|
452
|
+
<div class="row">
|
|
453
|
+
<!-- Left Column - Antenna 1 Controls -->
|
|
454
|
+
<div class="col-md-3">
|
|
455
|
+
{#if !isExternalMode}
|
|
456
|
+
<AntennaControls
|
|
457
|
+
antennas={antennas}
|
|
458
|
+
selectedAntenna={selectedAntenna}
|
|
459
|
+
antennaNumber={1}
|
|
460
|
+
electricalTiltIndex={ant1ElectricalTilt}
|
|
461
|
+
mechanicalTilt={ant1MechanicalTilt}
|
|
462
|
+
colorTheme="primary"
|
|
463
|
+
onAntennaChange={handleAntenna1Change}
|
|
464
|
+
onElectricalTiltChange={handleTilt1Change}
|
|
465
|
+
onMechanicalTiltChange={handleMechTilt1Change}
|
|
466
|
+
/>
|
|
467
|
+
{:else}
|
|
468
|
+
<!-- External mode: Show antenna info card -->
|
|
469
|
+
<div class="card border-primary">
|
|
470
|
+
<div class="card-header bg-primary text-white">
|
|
471
|
+
<i class="bi bi-broadcast me-2"></i>
|
|
472
|
+
{antenna1Label || 'Antenna 1'}
|
|
473
|
+
</div>
|
|
474
|
+
<div class="card-body">
|
|
475
|
+
{#if selectedAntenna}
|
|
476
|
+
<h6 class="card-title">{selectedAntenna.name}</h6>
|
|
477
|
+
<div class="small text-muted">
|
|
478
|
+
<div><strong>Band:</strong> {selectedAntenna.frequency}</div>
|
|
479
|
+
<div><strong>E-Tilt:</strong> {availableElectricalTilts[ant1ElectricalTilt] || 0}°</div>
|
|
480
|
+
<div><strong>M-Tilt:</strong> {ant1MechanicalTilt}°</div>
|
|
481
|
+
{#if selectedAntenna.gain_dBd}
|
|
482
|
+
<div><strong>Gain:</strong> {selectedAntenna.gain_dBd} dBd</div>
|
|
483
|
+
{/if}
|
|
484
|
+
</div>
|
|
485
|
+
{:else}
|
|
486
|
+
<p class="text-muted mb-0">Antenna not found in database</p>
|
|
487
|
+
{/if}
|
|
488
|
+
</div>
|
|
489
|
+
</div>
|
|
490
|
+
{/if}
|
|
491
|
+
</div>
|
|
492
|
+
|
|
493
|
+
<!-- Center Column - Chart -->
|
|
494
|
+
<div class="col-md-6">
|
|
495
|
+
<div class="card">
|
|
496
|
+
<div class="card-body p-1">
|
|
497
|
+
{#if chartEngine === 'polar-line'}
|
|
498
|
+
<PolarLineChart
|
|
499
|
+
{selectedAntenna}
|
|
500
|
+
{selectedAntenna2}
|
|
501
|
+
{viewMode}
|
|
502
|
+
{patternType}
|
|
503
|
+
{patternDisplayMode}
|
|
504
|
+
{ant1ElectricalTilt}
|
|
505
|
+
{ant1MechanicalTilt}
|
|
506
|
+
{ant2ElectricalTilt}
|
|
507
|
+
{ant2MechanicalTilt}
|
|
508
|
+
title={generateChartTitle()}
|
|
509
|
+
/>
|
|
510
|
+
{:else if chartEngine === 'polar-area'}
|
|
511
|
+
<PolarAreaChart
|
|
512
|
+
{selectedAntenna}
|
|
513
|
+
{selectedAntenna2}
|
|
514
|
+
{viewMode}
|
|
515
|
+
{patternType}
|
|
516
|
+
{patternDisplayMode}
|
|
517
|
+
{ant1ElectricalTilt}
|
|
518
|
+
{ant1MechanicalTilt}
|
|
519
|
+
{ant2ElectricalTilt}
|
|
520
|
+
{ant2MechanicalTilt}
|
|
521
|
+
title={generateChartTitle()}
|
|
522
|
+
/>
|
|
523
|
+
{/if}
|
|
524
|
+
</div>
|
|
525
|
+
</div>
|
|
526
|
+
</div>
|
|
527
|
+
|
|
528
|
+
<!-- Right Column - Antenna 2 Controls (only in compare mode) -->
|
|
529
|
+
<div class="col-md-3">
|
|
530
|
+
{#if viewMode === 'compare'}
|
|
531
|
+
{#if !isExternalMode}
|
|
532
|
+
<AntennaControls
|
|
533
|
+
antennas={antennas}
|
|
534
|
+
selectedAntenna={selectedAntenna2}
|
|
535
|
+
antennaNumber={2}
|
|
536
|
+
electricalTiltIndex={ant2ElectricalTilt}
|
|
537
|
+
mechanicalTilt={ant2MechanicalTilt}
|
|
538
|
+
colorTheme="warning"
|
|
539
|
+
onAntennaChange={handleAntenna2Change}
|
|
540
|
+
onElectricalTiltChange={handleTilt2Change}
|
|
541
|
+
onMechanicalTiltChange={handleMechTilt2Change}
|
|
542
|
+
/>
|
|
543
|
+
{:else}
|
|
544
|
+
<!-- External mode: Show antenna 2 info card -->
|
|
545
|
+
<div class="card border-warning">
|
|
546
|
+
<div class="card-header bg-warning text-dark">
|
|
547
|
+
<i class="bi bi-broadcast me-2"></i>
|
|
548
|
+
{antenna2Label || 'Antenna 2'}
|
|
549
|
+
</div>
|
|
550
|
+
<div class="card-body">
|
|
551
|
+
{#if selectedAntenna2}
|
|
552
|
+
<h6 class="card-title">{selectedAntenna2.name}</h6>
|
|
553
|
+
<div class="small text-muted">
|
|
554
|
+
<div><strong>Band:</strong> {selectedAntenna2.frequency}</div>
|
|
555
|
+
<div><strong>E-Tilt:</strong> {getAvailableTiltsForAntenna(selectedAntenna2)[ant2ElectricalTilt] || 0}°</div>
|
|
556
|
+
<div><strong>M-Tilt:</strong> {ant2MechanicalTilt}°</div>
|
|
557
|
+
{#if selectedAntenna2.gain_dBd}
|
|
558
|
+
<div><strong>Gain:</strong> {selectedAntenna2.gain_dBd} dBd</div>
|
|
559
|
+
{/if}
|
|
560
|
+
</div>
|
|
561
|
+
{:else}
|
|
562
|
+
<p class="text-muted mb-0">Antenna not found in database</p>
|
|
563
|
+
{/if}
|
|
564
|
+
</div>
|
|
565
|
+
</div>
|
|
566
|
+
{/if}
|
|
567
|
+
{:else}
|
|
568
|
+
<!-- Empty space to keep chart centered in single mode -->
|
|
569
|
+
<div></div>
|
|
570
|
+
{/if}
|
|
571
|
+
</div>
|
|
572
|
+
</div>
|
|
573
|
+
{/if}
|
|
574
|
+
</div>
|
|
575
|
+
|
|
576
|
+
<!-- Settings Modal (only in standalone mode) -->
|
|
577
|
+
{#if !hideSettings}
|
|
578
|
+
<AntennaSettingsModal
|
|
579
|
+
show={showSettingsModal}
|
|
580
|
+
onClose={closeSettings}
|
|
581
|
+
onDataRefresh={handleDataRefresh}
|
|
582
|
+
/>
|
|
583
|
+
{/if}
|
|
584
|
+
|
|
585
|
+
<style>
|
|
586
|
+
/* Chart container */
|
|
587
|
+
:global(.plotly .plot-container) {
|
|
588
|
+
border-radius: 8px !important;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
/* Main chart card styling */
|
|
592
|
+
.card {
|
|
593
|
+
border: none;
|
|
594
|
+
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
|
595
|
+
border-radius: 12px;
|
|
596
|
+
}
|
|
597
|
+
</style>
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { ExternalAntennaInput, ViewMode } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* AntennaTools - Main antenna pattern visualization component
|
|
4
|
+
*
|
|
5
|
+
* Two operating modes:
|
|
6
|
+
* 1. External Data Mode: Parent provides antenna specs via externalAntenna1/externalAntenna2 props
|
|
7
|
+
* - Antenna selection is hidden, tilts come from external data
|
|
8
|
+
* - Parent controls which antenna is displayed
|
|
9
|
+
*
|
|
10
|
+
* 2. Standalone Mode (default): User selects antennas from database
|
|
11
|
+
* - Full UI with antenna selection dropdowns
|
|
12
|
+
* - Settings modal for data management
|
|
13
|
+
*/
|
|
14
|
+
interface Props {
|
|
15
|
+
/** External antenna 1 specification (activates external mode) */
|
|
16
|
+
externalAntenna1?: ExternalAntennaInput | null;
|
|
17
|
+
/** External antenna 2 specification (for compare mode) */
|
|
18
|
+
externalAntenna2?: ExternalAntennaInput | null;
|
|
19
|
+
/** Initial antenna 1 name to pre-select (standalone mode only) */
|
|
20
|
+
initialAntenna1Name?: string;
|
|
21
|
+
/** Initial antenna 2 name to pre-select (standalone mode only) */
|
|
22
|
+
initialAntenna2Name?: string;
|
|
23
|
+
/** Initial electrical tilt for antenna 1 */
|
|
24
|
+
initialEtilt1?: number;
|
|
25
|
+
/** Initial electrical tilt for antenna 2 */
|
|
26
|
+
initialEtilt2?: number;
|
|
27
|
+
/** Initial mechanical tilt for antenna 1 */
|
|
28
|
+
initialMtilt1?: number;
|
|
29
|
+
/** Initial mechanical tilt for antenna 2 */
|
|
30
|
+
initialMtilt2?: number;
|
|
31
|
+
/** Initial view mode */
|
|
32
|
+
initialViewMode?: ViewMode;
|
|
33
|
+
/** Label for antenna 1 (e.g., cell name) */
|
|
34
|
+
antenna1Label?: string;
|
|
35
|
+
/** Label for antenna 2 (e.g., cell name) */
|
|
36
|
+
antenna2Label?: string;
|
|
37
|
+
/** Hide the settings button */
|
|
38
|
+
hideSettings?: boolean;
|
|
39
|
+
}
|
|
40
|
+
declare const AntennaTools: import("svelte").Component<Props, {}, "">;
|
|
41
|
+
type AntennaTools = ReturnType<typeof AntennaTools>;
|
|
42
|
+
export default AntennaTools;
|