@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,278 @@
1
+ <svelte:options runes={true} />
2
+
3
+ <script lang="ts">
4
+ import { onMount } from 'svelte';
5
+ import { loadAntennas } from '../utils/db-utils';
6
+ import { STANDARD_BANDS } from '../band-config';
7
+ import type { Antenna } from '../types';
8
+
9
+ let antennas = $state<Antenna[]>([]);
10
+ let isLoading = $state(true);
11
+ let searchQuery = $state('');
12
+ let selectedBand = $state<number | null>(null);
13
+
14
+ // Computed stats
15
+ let stats = $derived({
16
+ total: antennas.length,
17
+ uniqueModels: new Set(antennas.map(a => a.name)).size,
18
+ bands: [...new Set(antennas.map(a => a.frequency))].sort((a, b) => a - b),
19
+ });
20
+
21
+ // Filtered antennas based on search and band filter
22
+ let filteredAntennas = $derived(() => {
23
+ let result = antennas;
24
+
25
+ // Filter by band
26
+ if (selectedBand !== null) {
27
+ result = result.filter(a => a.frequency === selectedBand);
28
+ }
29
+
30
+ // Filter by search query
31
+ if (searchQuery.trim()) {
32
+ const query = searchQuery.toLowerCase();
33
+ result = result.filter(a =>
34
+ a.name.toLowerCase().includes(query) ||
35
+ a.tilt?.toString().includes(query)
36
+ );
37
+ }
38
+
39
+ return result;
40
+ });
41
+
42
+ // Group antennas by model name for display
43
+ let groupedAntennas = $derived(() => {
44
+ const groups = new Map<string, Antenna[]>();
45
+
46
+ for (const antenna of filteredAntennas()) {
47
+ const existing = groups.get(antenna.name) || [];
48
+ existing.push(antenna);
49
+ groups.set(antenna.name, existing);
50
+ }
51
+
52
+ // Sort groups by name
53
+ return Array.from(groups.entries()).sort((a, b) => a[0].localeCompare(b[0]));
54
+ });
55
+
56
+ async function loadData() {
57
+ try {
58
+ isLoading = true;
59
+ antennas = await loadAntennas();
60
+ } catch (error) {
61
+ console.error('Failed to load antennas:', error);
62
+ } finally {
63
+ isLoading = false;
64
+ }
65
+ }
66
+
67
+ function clearFilters() {
68
+ searchQuery = '';
69
+ selectedBand = null;
70
+ }
71
+
72
+ onMount(() => {
73
+ loadData();
74
+ });
75
+ </script>
76
+
77
+ <div class="database-viewer">
78
+ {#if isLoading}
79
+ <div class="text-center py-5">
80
+ <div class="spinner-border text-primary" role="status">
81
+ <span class="visually-hidden">Loading...</span>
82
+ </div>
83
+ <p class="text-muted mt-2">Loading antenna data...</p>
84
+ </div>
85
+ {:else if antennas.length === 0}
86
+ <div class="text-center py-5">
87
+ <i class="bi bi-database text-muted" style="font-size: 3rem;"></i>
88
+ <h5 class="mt-3">No Antenna Data</h5>
89
+ <p class="text-muted">Import MSI files or JSON data to populate the database.</p>
90
+ </div>
91
+ {:else}
92
+ <!-- Summary Stats -->
93
+ <div class="row g-3 mb-4">
94
+ <div class="col-md-4">
95
+ <div class="card bg-primary text-white h-100">
96
+ <div class="card-body text-center">
97
+ <h2 class="mb-0">{stats.total}</h2>
98
+ <small>Total Antennas</small>
99
+ </div>
100
+ </div>
101
+ </div>
102
+ <div class="col-md-4">
103
+ <div class="card bg-success text-white h-100">
104
+ <div class="card-body text-center">
105
+ <h2 class="mb-0">{stats.uniqueModels}</h2>
106
+ <small>Unique Models</small>
107
+ </div>
108
+ </div>
109
+ </div>
110
+ <div class="col-md-4">
111
+ <div class="card bg-info text-white h-100">
112
+ <div class="card-body text-center">
113
+ <h2 class="mb-0">{stats.bands.length}</h2>
114
+ <small>Frequency Bands</small>
115
+ </div>
116
+ </div>
117
+ </div>
118
+ </div>
119
+
120
+ <!-- Search and Filter -->
121
+ <div class="card mb-4">
122
+ <div class="card-body">
123
+ <div class="row g-3 align-items-end">
124
+ <div class="col-md-5">
125
+ <label class="form-label" for="searchInput">
126
+ <i class="bi bi-search me-1"></i>Search
127
+ </label>
128
+ <input
129
+ id="searchInput"
130
+ type="text"
131
+ class="form-control"
132
+ placeholder="Search by antenna name..."
133
+ bind:value={searchQuery}
134
+ />
135
+ </div>
136
+ <div class="col-md-5">
137
+ <label class="form-label">Filter by Band</label>
138
+ <div class="btn-group w-100 flex-wrap" role="group">
139
+ <button
140
+ type="button"
141
+ class="btn btn-sm {selectedBand === null ? 'btn-primary' : 'btn-outline-primary'}"
142
+ onclick={() => selectedBand = null}
143
+ >
144
+ All
145
+ </button>
146
+ {#each STANDARD_BANDS as band}
147
+ <button
148
+ type="button"
149
+ class="btn btn-sm {selectedBand === parseInt(band.name) ? 'btn-primary' : 'btn-outline-primary'}"
150
+ onclick={() => selectedBand = parseInt(band.name)}
151
+ disabled={!stats.bands.includes(parseInt(band.name))}
152
+ >
153
+ {band.name}
154
+ </button>
155
+ {/each}
156
+ </div>
157
+ </div>
158
+ <div class="col-md-2">
159
+ <button
160
+ type="button"
161
+ class="btn btn-outline-secondary w-100"
162
+ onclick={clearFilters}
163
+ >
164
+ <i class="bi bi-x-circle me-1"></i>Clear
165
+ </button>
166
+ </div>
167
+ </div>
168
+ </div>
169
+ </div>
170
+
171
+ <!-- Results Info -->
172
+ <div class="d-flex justify-content-between align-items-center mb-3">
173
+ <span class="text-muted">
174
+ Showing {filteredAntennas().length} of {antennas.length} antennas
175
+ {#if groupedAntennas().length > 0}
176
+ ({groupedAntennas().length} models)
177
+ {/if}
178
+ </span>
179
+ <button type="button" class="btn btn-sm btn-outline-primary" onclick={loadData}>
180
+ <i class="bi bi-arrow-clockwise me-1"></i>Refresh
181
+ </button>
182
+ </div>
183
+
184
+ <!-- Antenna Table -->
185
+ <div class="table-responsive" style="max-height: 400px; overflow-y: auto;">
186
+ <table class="table table-sm table-hover table-striped mb-0">
187
+ <thead class="table-light sticky-top">
188
+ <tr>
189
+ <th>Model Name</th>
190
+ <th class="text-center" style="width: 80px;">Band</th>
191
+ <th class="text-center" style="width: 100px;">Orig. Freq</th>
192
+ <th class="text-center" style="width: 70px;">Tilt</th>
193
+ <th class="text-center" style="width: 80px;">Gain</th>
194
+ <th class="text-center" style="width: 60px;">H-Pts</th>
195
+ <th class="text-center" style="width: 60px;">V-Pts</th>
196
+ </tr>
197
+ </thead>
198
+ <tbody>
199
+ {#each filteredAntennas() as antenna (antenna.id ?? `${antenna.name}-${antenna.frequency}-${antenna.tilt}`)}
200
+ <tr>
201
+ <td>
202
+ <span class="fw-medium">{antenna.name}</span>
203
+ {#if antenna.polarization}
204
+ <span class="badge bg-secondary ms-1">{antenna.polarization}</span>
205
+ {/if}
206
+ </td>
207
+ <td class="text-center">
208
+ <span class="badge bg-primary">{antenna.frequency}</span>
209
+ </td>
210
+ <td class="text-center text-muted">
211
+ {antenna.originalFrequency} MHz
212
+ </td>
213
+ <td class="text-center">
214
+ {antenna.tilt}°
215
+ </td>
216
+ <td class="text-center">
217
+ {antenna.gain_dBd?.toFixed(1) ?? '-'} dBd
218
+ </td>
219
+ <td class="text-center text-muted">
220
+ {antenna.pattern?.length ?? 0}
221
+ </td>
222
+ <td class="text-center text-muted">
223
+ {antenna.vertical_pattern?.length ?? 0}
224
+ </td>
225
+ </tr>
226
+ {:else}
227
+ <tr>
228
+ <td colspan="7" class="text-center text-muted py-4">
229
+ No antennas match your search criteria
230
+ </td>
231
+ </tr>
232
+ {/each}
233
+ </tbody>
234
+ </table>
235
+ </div>
236
+
237
+ <!-- Band Summary -->
238
+ {#if stats.bands.length > 0}
239
+ <div class="mt-4">
240
+ <h6 class="text-muted mb-2">
241
+ <i class="bi bi-bar-chart me-1"></i>Antennas per Band
242
+ </h6>
243
+ <div class="d-flex flex-wrap gap-2">
244
+ {#each STANDARD_BANDS as band}
245
+ {@const count = antennas.filter(a => a.frequency === parseInt(band.name)).length}
246
+ {#if count > 0}
247
+ <div class="badge bg-light text-dark border">
248
+ <strong>{band.name}</strong>: {count}
249
+ </div>
250
+ {/if}
251
+ {/each}
252
+ </div>
253
+ </div>
254
+ {/if}
255
+ {/if}
256
+ </div>
257
+
258
+ <style>
259
+ .database-viewer {
260
+ min-height: 300px;
261
+ }
262
+
263
+ .table th {
264
+ font-weight: 600;
265
+ font-size: 0.85rem;
266
+ }
267
+
268
+ .table td {
269
+ font-size: 0.875rem;
270
+ vertical-align: middle;
271
+ }
272
+
273
+ .sticky-top {
274
+ position: sticky;
275
+ top: 0;
276
+ z-index: 1;
277
+ }
278
+ </style>
@@ -0,0 +1,3 @@
1
+ declare const DatabaseViewer: import("svelte").Component<Record<string, never>, {}, "">;
2
+ type DatabaseViewer = ReturnType<typeof DatabaseViewer>;
3
+ export default DatabaseViewer;
@@ -0,0 +1,67 @@
1
+ <script lang="ts">
2
+ import { fade, fly } from 'svelte/transition';
3
+ import { dataOperationStatus } from '../stores/db-status';
4
+ import { onDestroy } from 'svelte';
5
+
6
+ let visible = false;
7
+ let message = '';
8
+ let type: 'success' | 'error' | 'info' = 'info';
9
+ let timer: ReturnType<typeof setTimeout> | undefined;
10
+
11
+ const unsubscribe = dataOperationStatus.subscribe(status => {
12
+ if (!status.operation) return;
13
+
14
+ // Clear any existing timer
15
+ if (timer) clearTimeout(timer);
16
+
17
+ if (status.inProgress) {
18
+ visible = true;
19
+ message = status.message || `${status.operation} in progress...`;
20
+ type = 'info';
21
+ } else if (status.success) {
22
+ visible = true;
23
+ message = status.message || `${status.operation} completed successfully`;
24
+ type = 'success';
25
+
26
+ // Auto-hide success messages after 4 seconds
27
+ timer = setTimeout(() => {
28
+ visible = false;
29
+ }, 4000);
30
+ } else if (status.error) {
31
+ visible = true;
32
+ message = status.message || `${status.operation} failed: ${status.error}`;
33
+ type = 'error';
34
+
35
+ // Auto-hide error messages after 6 seconds
36
+ timer = setTimeout(() => {
37
+ visible = false;
38
+ }, 6000);
39
+ }
40
+ });
41
+
42
+ onDestroy(() => {
43
+ unsubscribe();
44
+ if (timer) clearTimeout(timer);
45
+ });
46
+
47
+ function dismiss() {
48
+ visible = false;
49
+ if (timer) clearTimeout(timer);
50
+ }
51
+ </script>
52
+
53
+ {#if visible}
54
+ <div class="toast-container position-fixed bottom-0 end-0 p-3" transition:fly={{ y: 32, duration: 200 }}>
55
+ <div
56
+ class={`toast show align-items-center text-bg-${type === 'error' ? 'danger' : type === 'success' ? 'success' : 'info'} border-0`}
57
+ role="alert"
58
+ aria-live="assertive"
59
+ aria-atomic="true"
60
+ >
61
+ <div class="d-flex">
62
+ <div class="toast-body">{message}</div>
63
+ <button type="button" class="btn-close btn-close-white me-2 m-auto" aria-label="Close" on:click={dismiss}></button>
64
+ </div>
65
+ </div>
66
+ </div>
67
+ {/if}
@@ -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 DbNotification: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
15
+ [evt: string]: CustomEvent<any>;
16
+ }, {}, {}, string>;
17
+ type DbNotification = InstanceType<typeof DbNotification>;
18
+ export default DbNotification;
@@ -0,0 +1,115 @@
1
+ <svelte:options runes={true} />
2
+
3
+ <script lang="ts">
4
+ import { importFromJson, hasImportedData } from '../utils/db-utils';
5
+ import type { Antenna } from '../types';
6
+
7
+ interface Props {
8
+ onDataRefresh?: () => void;
9
+ }
10
+
11
+ let { onDataRefresh }: Props = $props();
12
+
13
+ let fileInput: HTMLInputElement | null = $state(null);
14
+
15
+ let isLoading = $state(false);
16
+ let message = $state('');
17
+ let error = $state('');
18
+ let antennaCount = $state(0);
19
+
20
+ async function handleFileUpload(event: Event) {
21
+ const input = event.target as HTMLInputElement;
22
+ fileInput = input;
23
+ const files = input.files;
24
+
25
+ if (!files || files.length === 0) return;
26
+
27
+ const file = files[0];
28
+ if (file.type !== 'application/json') {
29
+ error = 'Please select a JSON file.';
30
+ return;
31
+ }
32
+
33
+ try {
34
+ isLoading = true;
35
+ error = '';
36
+ message = 'Importing data...';
37
+
38
+ const antennas = await importFromJson(file);
39
+ antennaCount = antennas.length;
40
+
41
+ message = `Successfully imported ${antennaCount} antennas into the database.`;
42
+
43
+ // Notify parent of data refresh
44
+ onDataRefresh?.();
45
+
46
+ // Reset the file input
47
+ input.value = '';
48
+ } catch (e) {
49
+ if (e instanceof Error) {
50
+ error = `Error importing data: ${e.message}`;
51
+ } else {
52
+ error = 'An unknown error occurred during import.';
53
+ }
54
+ } finally {
55
+ isLoading = false;
56
+ }
57
+ }
58
+ </script>
59
+
60
+ <div class="card border-0 shadow-sm">
61
+ <div class="card-body ">
62
+
63
+ {#if hasImportedData()}
64
+ <div class="alert alert-warning border-0 bg-warning-subtle text-warning-emphasis d-flex align-items-start gap-2" role="alert">
65
+ <i class="bi bi-exclamation-triangle"></i>
66
+ <div>
67
+ <strong>Existing data detected.</strong> The new file will overwrite your current antenna records.
68
+ </div>
69
+ </div>
70
+ {/if}
71
+
72
+ <div class="mb-4">
73
+ <label class="form-label fw-semibold" for="jsonFileInput">JSON file</label>
74
+ <input
75
+ id="jsonFileInput"
76
+ class="form-control form-control-lg"
77
+ type="file"
78
+ accept=".json"
79
+ onchange={handleFileUpload}
80
+ disabled={isLoading}
81
+ bind:this={fileInput}
82
+ />
83
+ <div class="form-text">Choose a `.json` export created by this tool or generated from MSI conversions.</div>
84
+ </div>
85
+
86
+ <div class="d-flex align-items-center gap-3 mb-4">
87
+ <button class="btn btn-primary" type="button" onclick={() => fileInput && fileInput.click()} disabled={isLoading}>
88
+ <i class="bi bi-folder2-open me-2"></i>
89
+ Browse files
90
+ </button>
91
+ {#if isLoading}
92
+ <div class="d-flex align-items-center gap-2 text-muted">
93
+ <div class="spinner-border spinner-border-sm" role="status">
94
+ <span class="visually-hidden">Loading...</span>
95
+ </div>
96
+ <span>Importing data…</span>
97
+ </div>
98
+ {/if}
99
+ </div>
100
+
101
+ {#if message && !error}
102
+ <div class="alert alert-info border-0 bg-info-subtle text-info-emphasis d-flex align-items-start gap-2" role="alert">
103
+ <i class="bi bi-info-circle"></i>
104
+ <div>{message}</div>
105
+ </div>
106
+ {/if}
107
+
108
+ {#if error}
109
+ <div class="alert alert-danger border-0 bg-danger-subtle text-danger-emphasis d-flex align-items-start gap-2" role="alert">
110
+ <i class="bi bi-exclamation-octagon"></i>
111
+ <div>{error}</div>
112
+ </div>
113
+ {/if}
114
+ </div>
115
+ </div>
@@ -0,0 +1,6 @@
1
+ interface Props {
2
+ onDataRefresh?: () => void;
3
+ }
4
+ declare const JsonImporter: import("svelte").Component<Props, {}, "">;
5
+ type JsonImporter = ReturnType<typeof JsonImporter>;
6
+ export default JsonImporter;