@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.
Files changed (53) hide show
  1. package/dist/apps/antenna-tools/band-config.d.ts +53 -0
  2. package/dist/apps/antenna-tools/band-config.js +112 -0
  3. package/dist/apps/antenna-tools/components/AntennaControls.svelte +558 -0
  4. package/dist/apps/antenna-tools/components/AntennaControls.svelte.d.ts +16 -0
  5. package/dist/apps/antenna-tools/components/AntennaSettingsModal.svelte +304 -0
  6. package/dist/apps/antenna-tools/components/AntennaSettingsModal.svelte.d.ts +8 -0
  7. package/dist/apps/antenna-tools/components/AntennaTools.svelte +597 -0
  8. package/dist/apps/antenna-tools/components/AntennaTools.svelte.d.ts +42 -0
  9. package/dist/apps/antenna-tools/components/DatabaseViewer.svelte +278 -0
  10. package/dist/apps/antenna-tools/components/DatabaseViewer.svelte.d.ts +3 -0
  11. package/dist/apps/antenna-tools/components/DbNotification.svelte +67 -0
  12. package/dist/apps/antenna-tools/components/DbNotification.svelte.d.ts +18 -0
  13. package/dist/apps/antenna-tools/components/JsonImporter.svelte +115 -0
  14. package/dist/apps/antenna-tools/components/JsonImporter.svelte.d.ts +6 -0
  15. package/dist/apps/antenna-tools/components/MSIConverter.svelte +282 -0
  16. package/dist/apps/antenna-tools/components/MSIConverter.svelte.d.ts +6 -0
  17. package/dist/apps/antenna-tools/components/chart-engines/PolarAreaChart.svelte +123 -0
  18. package/dist/apps/antenna-tools/components/chart-engines/PolarAreaChart.svelte.d.ts +16 -0
  19. package/dist/apps/antenna-tools/components/chart-engines/PolarLineChart.svelte +123 -0
  20. package/dist/apps/antenna-tools/components/chart-engines/PolarLineChart.svelte.d.ts +16 -0
  21. package/dist/apps/antenna-tools/components/chart-engines/index.d.ts +9 -0
  22. package/dist/apps/antenna-tools/components/chart-engines/index.js +9 -0
  23. package/dist/apps/antenna-tools/components/index.d.ts +8 -0
  24. package/dist/apps/antenna-tools/components/index.js +10 -0
  25. package/dist/apps/antenna-tools/db.d.ts +28 -0
  26. package/dist/apps/antenna-tools/db.js +45 -0
  27. package/dist/apps/antenna-tools/index.d.ts +26 -0
  28. package/dist/apps/antenna-tools/index.js +40 -0
  29. package/dist/apps/antenna-tools/stores/antennas.d.ts +13 -0
  30. package/dist/apps/antenna-tools/stores/antennas.js +25 -0
  31. package/dist/apps/antenna-tools/stores/db-status.d.ts +32 -0
  32. package/dist/apps/antenna-tools/stores/db-status.js +38 -0
  33. package/dist/apps/antenna-tools/stores/index.d.ts +5 -0
  34. package/dist/apps/antenna-tools/stores/index.js +5 -0
  35. package/dist/apps/antenna-tools/types.d.ts +137 -0
  36. package/dist/apps/antenna-tools/types.js +16 -0
  37. package/dist/apps/antenna-tools/utils/antenna-helpers.d.ts +83 -0
  38. package/dist/apps/antenna-tools/utils/antenna-helpers.js +198 -0
  39. package/dist/apps/antenna-tools/utils/chart-engines/index.d.ts +5 -0
  40. package/dist/apps/antenna-tools/utils/chart-engines/index.js +5 -0
  41. package/dist/apps/antenna-tools/utils/chart-engines/polar-area-utils.d.ts +94 -0
  42. package/dist/apps/antenna-tools/utils/chart-engines/polar-area-utils.js +151 -0
  43. package/dist/apps/antenna-tools/utils/chart-engines/polar-line-utils.d.ts +93 -0
  44. package/dist/apps/antenna-tools/utils/chart-engines/polar-line-utils.js +139 -0
  45. package/dist/apps/antenna-tools/utils/db-utils.d.ts +50 -0
  46. package/dist/apps/antenna-tools/utils/db-utils.js +266 -0
  47. package/dist/apps/antenna-tools/utils/index.d.ts +7 -0
  48. package/dist/apps/antenna-tools/utils/index.js +7 -0
  49. package/dist/apps/antenna-tools/utils/msi-parser.d.ts +21 -0
  50. package/dist/apps/antenna-tools/utils/msi-parser.js +215 -0
  51. package/dist/apps/antenna-tools/utils/recent-antennas.d.ts +24 -0
  52. package/dist/apps/antenna-tools/utils/recent-antennas.js +64 -0
  53. package/package.json +1 -1
@@ -0,0 +1,282 @@
1
+ <svelte:options runes={true} />
2
+
3
+ <script lang="ts">
4
+ import { parseFolder } from '../utils/msi-parser';
5
+ import { saveAntennasWithPurge, type ImportResult } from '../utils/db-utils';
6
+ import { STANDARD_BANDS } from '../band-config';
7
+ import type { RawAntenna, Antenna } from '../types';
8
+
9
+ interface Props {
10
+ onDataRefresh?: () => void;
11
+ }
12
+
13
+ let { onDataRefresh }: Props = $props();
14
+
15
+ let rawAntennas = $state<RawAntenna[]>([]);
16
+ let isLoading = $state(false);
17
+ let error = $state('');
18
+ let message = $state('');
19
+ let importResult = $state<ImportResult | null>(null);
20
+
21
+ let recursiveScan = $state(true);
22
+
23
+ async function selectFolder() {
24
+ try {
25
+ // Check if File System Access API is supported
26
+ if (!('showDirectoryPicker' in window)) {
27
+ error = 'Your browser does not support the File System Access API. Please use a Chromium-based browser.';
28
+ return;
29
+ }
30
+
31
+ isLoading = true;
32
+ error = '';
33
+ message = 'Selecting folder...';
34
+ importResult = null;
35
+
36
+ // Ask user to select a folder
37
+ const directoryPicker = window.showDirectoryPicker as () => Promise<FileSystemDirectoryHandle>;
38
+ const directoryHandle = await directoryPicker();
39
+ message = recursiveScan ?
40
+ 'Reading and parsing files recursively (including all subfolders)...' :
41
+ 'Reading and parsing files (top-level folder only)...';
42
+
43
+ // Parse all MSI/PNT files in the folder, with recursive option
44
+ rawAntennas = await parseFolder(directoryHandle, recursiveScan);
45
+
46
+ if (rawAntennas.length === 0) {
47
+ message = 'No MSI or PNT files found in the selected folder.';
48
+ } else {
49
+ message = `Successfully parsed ${rawAntennas.length} antenna files${recursiveScan ? ' from all folders' : ''}.`;
50
+ }
51
+ } catch (e) {
52
+ if (e instanceof Error) {
53
+ if (e.name === 'AbortError') {
54
+ // User cancelled the folder picker
55
+ error = '';
56
+ message = 'Folder selection cancelled.';
57
+ } else {
58
+ error = `Error: ${e.message}`;
59
+ }
60
+ } else {
61
+ error = 'An unknown error occurred.';
62
+ }
63
+ } finally {
64
+ isLoading = false;
65
+ }
66
+ }
67
+
68
+ async function saveToDatabase() {
69
+ if (rawAntennas.length === 0) {
70
+ error = 'No antenna data to save.';
71
+ return;
72
+ }
73
+
74
+ try {
75
+ isLoading = true;
76
+ message = 'Purging frequencies and saving to database...';
77
+
78
+ // Save with automatic purge
79
+ importResult = await saveAntennasWithPurge(rawAntennas);
80
+
81
+ if (importResult.success) {
82
+ const stats = importResult.purgeStats;
83
+ message = `Successfully imported ${stats.totalAfter} antennas (${stats.reductionPercent}% reduction from ${stats.totalBefore} raw files).`;
84
+
85
+ // Notify parent of data refresh
86
+ onDataRefresh?.();
87
+ } else {
88
+ error = importResult.error || 'Failed to save antennas to database';
89
+ }
90
+
91
+ } catch (e) {
92
+ error = e instanceof Error ? e.message : 'Failed to save antennas to database';
93
+ } finally {
94
+ isLoading = false;
95
+ }
96
+ }
97
+
98
+ function downloadJson() {
99
+ if (rawAntennas.length === 0) {
100
+ error = 'No antenna data to download.';
101
+ return;
102
+ }
103
+
104
+ // Download raw (unpurged) data
105
+ const jsonData = JSON.stringify(rawAntennas, null, 2);
106
+ const blob = new Blob([jsonData], { type: 'application/json' });
107
+ const url = URL.createObjectURL(blob);
108
+ const link = document.createElement('a');
109
+ link.href = url;
110
+ link.download = `antenna-raw-${new Date().toISOString().split('T')[0]}.json`;
111
+ document.body.appendChild(link);
112
+ link.click();
113
+ document.body.removeChild(link);
114
+ URL.revokeObjectURL(url);
115
+
116
+ message = 'JSON file downloaded (raw data, not purged).';
117
+ }
118
+ </script>
119
+
120
+ <div class="card border-0 shadow-sm">
121
+ <div class="card-body">
122
+ <header class="d-flex flex-column flex-lg-row align-items-lg-center justify-content-between gap-3 mb-4">
123
+ <div>
124
+ <h1 class="h3 mb-2">Convert MSI / PNT to JSON</h1>
125
+ <p class="text-muted mb-0">Scan a folder of antenna definitions. Files will be purged to keep only standard bands.</p>
126
+ </div>
127
+ <span class="badge text-bg-success">Local processing</span>
128
+ </header>
129
+
130
+ <!-- Standard Bands Info -->
131
+ <div class="alert alert-info border-0 bg-info-subtle mb-4">
132
+ <strong><i class="bi bi-info-circle me-2"></i>Standard Bands:</strong>
133
+ <div class="d-flex flex-wrap gap-2 mt-2">
134
+ {#each STANDARD_BANDS as band}
135
+ <span class="badge text-bg-primary">{band.name} MHz ({band.dlMin}-{band.dlMax})</span>
136
+ {/each}
137
+ </div>
138
+ <small class="d-block mt-2 text-muted">Only antennas within these frequency ranges will be kept (closest to center frequency).</small>
139
+ </div>
140
+
141
+ <div class="d-flex flex-column gap-4">
142
+ <div class="text-center">
143
+ <button class="btn btn-primary btn-lg px-4" onclick={selectFolder} disabled={isLoading}>
144
+ {#if isLoading}
145
+ <span class="spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></span>
146
+ Processing…
147
+ {:else}
148
+ <i class="bi bi-folder-plus me-2"></i>
149
+ Choose folder with MSI / PNT files
150
+ {/if}
151
+ </button>
152
+ </div>
153
+
154
+ <div class="d-flex justify-content-center">
155
+ <div class="form-check form-switch">
156
+ <input class="form-check-input" type="checkbox" bind:checked={recursiveScan} id="recursiveScan" />
157
+ <label class="form-check-label" for="recursiveScan">
158
+ <i class="bi bi-arrow-down-circle me-1"></i>
159
+ Include sub-folders during scan
160
+ </label>
161
+ </div>
162
+ </div>
163
+
164
+ {#if rawAntennas.length > 0}
165
+ <div class="row g-3">
166
+ <div class="col-md-6">
167
+ <button class="btn btn-success w-100" onclick={saveToDatabase} disabled={isLoading}>
168
+ {#if isLoading}
169
+ <span class="spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></span>
170
+ Saving…
171
+ {:else}
172
+ <i class="bi bi-database-add me-2"></i>
173
+ Import to database (with purge)
174
+ {/if}
175
+ </button>
176
+ </div>
177
+ <div class="col-md-6">
178
+ <button class="btn btn-outline-secondary w-100" onclick={downloadJson}>
179
+ <i class="bi bi-download me-2"></i>
180
+ Download raw JSON
181
+ </button>
182
+ </div>
183
+ </div>
184
+ {/if}
185
+
186
+ {#if message && !error}
187
+ <div class="alert alert-info border-0 bg-info-subtle text-info-emphasis" role="alert">
188
+ <i class="bi bi-info-circle me-2"></i>
189
+ {message}
190
+ </div>
191
+ {/if}
192
+
193
+ {#if error}
194
+ <div class="alert alert-danger border-0 bg-danger-subtle text-danger-emphasis" role="alert">
195
+ <i class="bi bi-exclamation-octagon me-2"></i>
196
+ {error}
197
+ </div>
198
+ {/if}
199
+
200
+ <!-- Purge Results -->
201
+ {#if importResult?.success}
202
+ <div class="card border-success">
203
+ <div class="card-header bg-success text-white">
204
+ <i class="bi bi-check-circle me-2"></i>Import Complete
205
+ </div>
206
+ <div class="card-body">
207
+ <div class="row text-center">
208
+ <div class="col-4">
209
+ <div class="h3 text-muted">{importResult.purgeStats.totalBefore}</div>
210
+ <small class="text-muted">Raw files</small>
211
+ </div>
212
+ <div class="col-4">
213
+ <div class="h3 text-success">{importResult.purgeStats.totalAfter}</div>
214
+ <small class="text-muted">Kept</small>
215
+ </div>
216
+ <div class="col-4">
217
+ <div class="h3 text-danger">{importResult.purgeStats.removed}</div>
218
+ <small class="text-muted">Removed</small>
219
+ </div>
220
+ </div>
221
+
222
+ <hr>
223
+
224
+ <h6>Antennas per band:</h6>
225
+ <div class="d-flex flex-wrap gap-2">
226
+ {#each Object.entries(importResult.purgeStats.perBand) as [band, count]}
227
+ <span class="badge text-bg-secondary">{band} MHz: {count}</span>
228
+ {/each}
229
+ </div>
230
+ </div>
231
+ </div>
232
+ {/if}
233
+
234
+ {#if rawAntennas.length > 0 && !importResult}
235
+ <div class="card border border-light-subtle">
236
+ <div class="card-body">
237
+ <div class="d-flex flex-column flex-md-row align-items-md-center justify-content-between gap-3 mb-3">
238
+ <div class="d-flex align-items-center gap-3">
239
+ <span class="badge text-bg-warning fs-6">{rawAntennas.length}</span>
240
+ <h2 class="h5 mb-0">Raw files parsed (before purge)</h2>
241
+ </div>
242
+ <button
243
+ class="btn btn-link text-decoration-none p-0"
244
+ type="button"
245
+ data-bs-toggle="collapse"
246
+ data-bs-target="#parsedAntennaList"
247
+ aria-expanded="false"
248
+ >
249
+ <i class="bi bi-list-ul me-2"></i>
250
+ View details
251
+ </button>
252
+ </div>
253
+ <div class="collapse" id="parsedAntennaList">
254
+ <div class="list-group overflow-auto" style="max-height: 320px;">
255
+ {#each rawAntennas as antenna, index}
256
+ <div class="list-group-item border-0 border-bottom">
257
+ <div class="d-flex justify-content-between align-items-start">
258
+ <div>
259
+ <h3 class="h6 mb-1">{antenna.name}</h3>
260
+ <div class="d-flex flex-wrap gap-3 text-muted small">
261
+ <span><i class="bi bi-broadcast me-1"></i>{antenna.frequency} MHz</span>
262
+ <span><i class="bi bi-arrow-clockwise me-1"></i>Tilt {antenna.tilt}°</span>
263
+ </div>
264
+ {#if antenna.sourcePath}
265
+ <div class="text-muted small mt-1">
266
+ <i class="bi bi-folder me-1"></i>
267
+ {antenna.sourcePath}
268
+ </div>
269
+ {/if}
270
+ </div>
271
+ <span class="badge text-bg-light">#{index + 1}</span>
272
+ </div>
273
+ </div>
274
+ {/each}
275
+ </div>
276
+ </div>
277
+ </div>
278
+ </div>
279
+ {/if}
280
+ </div>
281
+ </div>
282
+ </div>
@@ -0,0 +1,6 @@
1
+ interface Props {
2
+ onDataRefresh?: () => void;
3
+ }
4
+ declare const MsiConverter: import("svelte").Component<Props, {}, "">;
5
+ type MsiConverter = ReturnType<typeof MsiConverter>;
6
+ export default MsiConverter;
@@ -0,0 +1,123 @@
1
+ <svelte:options runes={true} />
2
+
3
+ <script lang="ts">
4
+ import { onMount } from 'svelte';
5
+ import { browser } from '$app/environment';
6
+ import type { Antenna } from '../../types';
7
+ import {
8
+ createPolarAreaChartData,
9
+ createPolarAreaLayout,
10
+ type PolarAreaTraceData
11
+ } from '../../utils/chart-engines/polar-area-utils';
12
+
13
+ interface Props {
14
+ selectedAntenna?: Antenna | null;
15
+ selectedAntenna2?: Antenna | null;
16
+ viewMode?: 'single' | 'compare';
17
+ patternType?: 'horizontal' | 'vertical';
18
+ patternDisplayMode?: 'normalized' | 'gain-adjusted';
19
+ ant1ElectricalTilt?: number;
20
+ ant1MechanicalTilt?: number;
21
+ ant2ElectricalTilt?: number;
22
+ ant2MechanicalTilt?: number;
23
+ title?: string;
24
+ }
25
+
26
+ let {
27
+ selectedAntenna = null,
28
+ selectedAntenna2 = null,
29
+ viewMode = 'single',
30
+ patternType = 'vertical',
31
+ patternDisplayMode = 'normalized',
32
+ ant1ElectricalTilt = 0,
33
+ ant1MechanicalTilt = 0,
34
+ ant2ElectricalTilt = 0,
35
+ ant2MechanicalTilt = 0,
36
+ title = 'Antenna Pattern - Polar Area Chart'
37
+ }: Props = $props();
38
+
39
+ let Plotly: any;
40
+ let chartDiv = $state<HTMLDivElement>();
41
+ let chartInitialized = $state(false);
42
+
43
+ // Initialize Plotly on mount
44
+ onMount(async () => {
45
+ if (browser) {
46
+ try {
47
+ Plotly = await import('plotly.js-dist-min');
48
+ await initializeChart();
49
+ } catch (error) {
50
+ console.error('Failed to load Plotly:', error);
51
+ }
52
+ }
53
+ });
54
+
55
+ // Update chart when data changes
56
+ $effect(() => {
57
+ if (chartInitialized) {
58
+ updateChart();
59
+ }
60
+ });
61
+
62
+ async function initializeChart() {
63
+ if (!Plotly || !chartDiv) return;
64
+
65
+ const data = createPolarAreaChartData(
66
+ selectedAntenna,
67
+ selectedAntenna2,
68
+ viewMode,
69
+ patternType === 'horizontal',
70
+ patternType === 'vertical',
71
+ ant1ElectricalTilt,
72
+ ant1MechanicalTilt,
73
+ ant2ElectricalTilt,
74
+ ant2MechanicalTilt,
75
+ patternDisplayMode
76
+ );
77
+
78
+ const layout = createPolarAreaLayout(title);
79
+ const config = {
80
+ responsive: true,
81
+ displayModeBar: true,
82
+ displaylogo: false,
83
+ modeBarButtonsToRemove: [
84
+ 'zoom2d', 'pan2d', 'select2d', 'lasso2d',
85
+ 'zoomIn2d', 'zoomOut2d', 'autoScale2d',
86
+ 'resetScale2d', 'toggleSpikelines', 'toImage'
87
+ ]
88
+ };
89
+
90
+ try {
91
+ await Plotly.newPlot(chartDiv, data, layout, config);
92
+ chartInitialized = true;
93
+ } catch (error) {
94
+ console.error('Failed to create polar area chart:', error);
95
+ }
96
+ }
97
+
98
+ async function updateChart() {
99
+ if (!Plotly || !chartDiv || !chartInitialized) return;
100
+
101
+ try {
102
+ const data = createPolarAreaChartData(
103
+ selectedAntenna,
104
+ selectedAntenna2,
105
+ viewMode,
106
+ patternType === 'horizontal',
107
+ patternType === 'vertical',
108
+ ant1ElectricalTilt,
109
+ ant1MechanicalTilt,
110
+ ant2ElectricalTilt,
111
+ ant2MechanicalTilt,
112
+ patternDisplayMode
113
+ );
114
+
115
+ const layout = createPolarAreaLayout(title);
116
+ await Plotly.react(chartDiv, data, layout);
117
+ } catch (error) {
118
+ console.error('Failed to update polar area chart:', error);
119
+ }
120
+ }
121
+ </script>
122
+
123
+ <div bind:this={chartDiv} style="height: 700px;"></div>
@@ -0,0 +1,16 @@
1
+ import type { Antenna } from '../../types';
2
+ interface Props {
3
+ selectedAntenna?: Antenna | null;
4
+ selectedAntenna2?: Antenna | null;
5
+ viewMode?: 'single' | 'compare';
6
+ patternType?: 'horizontal' | 'vertical';
7
+ patternDisplayMode?: 'normalized' | 'gain-adjusted';
8
+ ant1ElectricalTilt?: number;
9
+ ant1MechanicalTilt?: number;
10
+ ant2ElectricalTilt?: number;
11
+ ant2MechanicalTilt?: number;
12
+ title?: string;
13
+ }
14
+ declare const PolarAreaChart: import("svelte").Component<Props, {}, "">;
15
+ type PolarAreaChart = ReturnType<typeof PolarAreaChart>;
16
+ export default PolarAreaChart;
@@ -0,0 +1,123 @@
1
+ <svelte:options runes={true} />
2
+
3
+ <script lang="ts">
4
+ import { onMount } from 'svelte';
5
+ import { browser } from '$app/environment';
6
+ import type { Antenna } from '../../types';
7
+ import {
8
+ createPolarLineChartData,
9
+ createPolarLineLayout,
10
+ type PolarLineTraceData
11
+ } from '../../utils/chart-engines/polar-line-utils';
12
+
13
+ interface Props {
14
+ selectedAntenna?: Antenna | null;
15
+ selectedAntenna2?: Antenna | null;
16
+ viewMode?: 'single' | 'compare';
17
+ patternType?: 'horizontal' | 'vertical';
18
+ patternDisplayMode?: 'normalized' | 'gain-adjusted';
19
+ ant1ElectricalTilt?: number;
20
+ ant1MechanicalTilt?: number;
21
+ ant2ElectricalTilt?: number;
22
+ ant2MechanicalTilt?: number;
23
+ title?: string;
24
+ }
25
+
26
+ let {
27
+ selectedAntenna = null,
28
+ selectedAntenna2 = null,
29
+ viewMode = 'single',
30
+ patternType = 'vertical',
31
+ patternDisplayMode = 'normalized',
32
+ ant1ElectricalTilt = 0,
33
+ ant1MechanicalTilt = 0,
34
+ ant2ElectricalTilt = 0,
35
+ ant2MechanicalTilt = 0,
36
+ title = 'Antenna Pattern - Polar Line Chart'
37
+ }: Props = $props();
38
+
39
+ let Plotly: any;
40
+ let chartDiv = $state<HTMLDivElement>();
41
+ let chartInitialized = $state(false);
42
+
43
+ // Initialize Plotly on mount
44
+ onMount(async () => {
45
+ if (browser) {
46
+ try {
47
+ Plotly = await import('plotly.js-dist-min');
48
+ await initializeChart();
49
+ } catch (error) {
50
+ console.error('Failed to load Plotly:', error);
51
+ }
52
+ }
53
+ });
54
+
55
+ // Update chart when data changes
56
+ $effect(() => {
57
+ if (chartInitialized) {
58
+ updateChart();
59
+ }
60
+ });
61
+
62
+ async function initializeChart() {
63
+ if (!Plotly || !chartDiv) return;
64
+
65
+ const data = createPolarLineChartData(
66
+ selectedAntenna,
67
+ selectedAntenna2,
68
+ viewMode,
69
+ patternType === 'horizontal',
70
+ patternType === 'vertical',
71
+ ant1ElectricalTilt,
72
+ ant1MechanicalTilt,
73
+ ant2ElectricalTilt,
74
+ ant2MechanicalTilt,
75
+ patternDisplayMode
76
+ );
77
+
78
+ const layout = createPolarLineLayout(title);
79
+ const config = {
80
+ responsive: true,
81
+ displayModeBar: true,
82
+ displaylogo: false,
83
+ modeBarButtonsToRemove: [
84
+ 'zoom2d', 'pan2d', 'select2d', 'lasso2d',
85
+ 'zoomIn2d', 'zoomOut2d', 'autoScale2d',
86
+ 'resetScale2d', 'toggleSpikelines', 'toImage'
87
+ ]
88
+ };
89
+
90
+ try {
91
+ await Plotly.newPlot(chartDiv, data, layout, config);
92
+ chartInitialized = true;
93
+ } catch (error) {
94
+ console.error('Failed to create polar line chart:', error);
95
+ }
96
+ }
97
+
98
+ async function updateChart() {
99
+ if (!Plotly || !chartDiv || !chartInitialized) return;
100
+
101
+ try {
102
+ const data = createPolarLineChartData(
103
+ selectedAntenna,
104
+ selectedAntenna2,
105
+ viewMode,
106
+ patternType === 'horizontal',
107
+ patternType === 'vertical',
108
+ ant1ElectricalTilt,
109
+ ant1MechanicalTilt,
110
+ ant2ElectricalTilt,
111
+ ant2MechanicalTilt,
112
+ patternDisplayMode
113
+ );
114
+
115
+ const layout = createPolarLineLayout(title);
116
+ await Plotly.react(chartDiv, data, layout);
117
+ } catch (error) {
118
+ console.error('Failed to update polar line chart:', error);
119
+ }
120
+ }
121
+ </script>
122
+
123
+ <div bind:this={chartDiv} style="height: 700px;"></div>
@@ -0,0 +1,16 @@
1
+ import type { Antenna } from '../../types';
2
+ interface Props {
3
+ selectedAntenna?: Antenna | null;
4
+ selectedAntenna2?: Antenna | null;
5
+ viewMode?: 'single' | 'compare';
6
+ patternType?: 'horizontal' | 'vertical';
7
+ patternDisplayMode?: 'normalized' | 'gain-adjusted';
8
+ ant1ElectricalTilt?: number;
9
+ ant1MechanicalTilt?: number;
10
+ ant2ElectricalTilt?: number;
11
+ ant2MechanicalTilt?: number;
12
+ title?: string;
13
+ }
14
+ declare const PolarLineChart: import("svelte").Component<Props, {}, "">;
15
+ type PolarLineChart = ReturnType<typeof PolarLineChart>;
16
+ export default PolarLineChart;
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Antenna Tools - Chart Engine Components Index
3
+ */
4
+ export { default as PolarLineChart } from './PolarLineChart.svelte';
5
+ export { default as PolarAreaChart } from './PolarAreaChart.svelte';
6
+ export type { PolarLineTraceData } from '../../utils/chart-engines/polar-line-utils';
7
+ export type { PolarAreaTraceData } from '../../utils/chart-engines/polar-area-utils';
8
+ export * from '../../utils/chart-engines/polar-line-utils';
9
+ export * from '../../utils/chart-engines/polar-area-utils';
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Antenna Tools - Chart Engine Components Index
3
+ */
4
+ // Chart Components
5
+ export { default as PolarLineChart } from './PolarLineChart.svelte';
6
+ export { default as PolarAreaChart } from './PolarAreaChart.svelte';
7
+ // Chart Utilities
8
+ export * from '../../utils/chart-engines/polar-line-utils';
9
+ export * from '../../utils/chart-engines/polar-area-utils';
@@ -0,0 +1,8 @@
1
+ export { default as AntennaTools } from './AntennaTools.svelte';
2
+ export { default as AntennaControls } from './AntennaControls.svelte';
3
+ export { default as AntennaSettingsModal } from './AntennaSettingsModal.svelte';
4
+ export { default as DatabaseViewer } from './DatabaseViewer.svelte';
5
+ export { default as DbNotification } from './DbNotification.svelte';
6
+ export { default as JsonImporter } from './JsonImporter.svelte';
7
+ export { default as MSIConverter } from './MSIConverter.svelte';
8
+ export { PolarLineChart, PolarAreaChart } from './chart-engines/index';
@@ -0,0 +1,10 @@
1
+ // Component exports for antenna-tools module
2
+ export { default as AntennaTools } from './AntennaTools.svelte';
3
+ export { default as AntennaControls } from './AntennaControls.svelte';
4
+ export { default as AntennaSettingsModal } from './AntennaSettingsModal.svelte';
5
+ export { default as DatabaseViewer } from './DatabaseViewer.svelte';
6
+ export { default as DbNotification } from './DbNotification.svelte';
7
+ export { default as JsonImporter } from './JsonImporter.svelte';
8
+ export { default as MSIConverter } from './MSIConverter.svelte';
9
+ // Chart engine exports
10
+ export { PolarLineChart, PolarAreaChart } from './chart-engines/index';
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Antenna Tools - Database
3
+ * Dexie-based IndexedDB for antenna pattern storage
4
+ */
5
+ import Dexie, { type Table } from 'dexie';
6
+ import type { Antenna } from './types';
7
+ declare class AntennaToolsDatabase extends Dexie {
8
+ antennas: Table<Antenna, number>;
9
+ constructor();
10
+ /**
11
+ * Check if database has any antenna data
12
+ */
13
+ hasData(): Promise<boolean>;
14
+ /**
15
+ * Get total antenna count
16
+ */
17
+ getCount(): Promise<number>;
18
+ /**
19
+ * Get unique antenna model names
20
+ */
21
+ getUniqueAntennaNames(): Promise<string[]>;
22
+ /**
23
+ * Get unique frequency bands in database
24
+ */
25
+ getUniqueBands(): Promise<number[]>;
26
+ }
27
+ export declare const db: AntennaToolsDatabase;
28
+ export type { Antenna } from './types';
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Antenna Tools - Database
3
+ * Dexie-based IndexedDB for antenna pattern storage
4
+ */
5
+ import Dexie, {} from 'dexie';
6
+ class AntennaToolsDatabase extends Dexie {
7
+ antennas;
8
+ constructor() {
9
+ super('antennaToolsDB');
10
+ // Version 1: Initial schema with band-based frequency
11
+ this.version(1).stores({
12
+ antennas: '++id, name, frequency, originalFrequency, tilt'
13
+ });
14
+ }
15
+ /**
16
+ * Check if database has any antenna data
17
+ */
18
+ async hasData() {
19
+ const count = await this.antennas.count();
20
+ return count > 0;
21
+ }
22
+ /**
23
+ * Get total antenna count
24
+ */
25
+ async getCount() {
26
+ return await this.antennas.count();
27
+ }
28
+ /**
29
+ * Get unique antenna model names
30
+ */
31
+ async getUniqueAntennaNames() {
32
+ const antennas = await this.antennas.toArray();
33
+ const names = new Set(antennas.map(a => a.name));
34
+ return Array.from(names).sort();
35
+ }
36
+ /**
37
+ * Get unique frequency bands in database
38
+ */
39
+ async getUniqueBands() {
40
+ const antennas = await this.antennas.toArray();
41
+ const bands = new Set(antennas.map(a => a.frequency));
42
+ return Array.from(bands).sort((a, b) => a - b);
43
+ }
44
+ }
45
+ export const db = new AntennaToolsDatabase();