@smartnet360/svelte-components 0.0.124 → 0.0.125
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/components/AntennaSettingsModal.svelte +4 -174
- package/dist/apps/antenna-tools/components/DatabaseViewer.svelte +2 -2
- package/dist/apps/antenna-tools/components/MSIConverter.svelte +302 -43
- package/dist/apps/antenna-tools/db.js +4 -0
- package/dist/apps/antenna-tools/utils/db-utils.d.ts +19 -0
- package/dist/apps/antenna-tools/utils/db-utils.js +108 -0
- package/dist/core/Auth/LoginForm.svelte +397 -0
- package/dist/core/Auth/LoginForm.svelte.d.ts +16 -0
- package/dist/core/Auth/auth.svelte.d.ts +22 -0
- package/dist/core/Auth/auth.svelte.js +184 -0
- package/dist/core/Auth/config.d.ts +25 -0
- package/dist/core/Auth/config.js +256 -0
- package/dist/core/Auth/index.d.ts +4 -0
- package/dist/core/Auth/index.js +5 -0
- package/dist/core/Auth/types.d.ts +140 -0
- package/dist/core/Auth/types.js +2 -0
- package/dist/core/LandingPage/App.svelte +102 -0
- package/dist/core/LandingPage/App.svelte.d.ts +20 -0
- package/dist/core/LandingPage/LandingPage.svelte +480 -0
- package/dist/core/LandingPage/LandingPage.svelte.d.ts +21 -0
- package/dist/core/LandingPage/index.d.ts +2 -0
- package/dist/core/LandingPage/index.js +3 -0
- package/dist/core/index.d.ts +2 -0
- package/dist/core/index.js +4 -0
- package/package.json +1 -1
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
<svelte:options runes={true} />
|
|
2
2
|
|
|
3
3
|
<script lang="ts">
|
|
4
|
-
import JsonImporter from './JsonImporter.svelte';
|
|
5
4
|
import MSIConverter from './MSIConverter.svelte';
|
|
6
|
-
import DbNotification from './DbNotification.svelte';
|
|
7
5
|
import DatabaseViewer from './DatabaseViewer.svelte';
|
|
8
|
-
import { exportAntennas, clearAllAntennas } from '../utils/db-utils';
|
|
9
6
|
import type { BandDefinition } from '../types';
|
|
10
7
|
|
|
11
8
|
interface Props {
|
|
@@ -18,42 +15,12 @@
|
|
|
18
15
|
|
|
19
16
|
let { show, onClose, onDataRefresh, customBands }: Props = $props();
|
|
20
17
|
|
|
21
|
-
let activeTab = $state<'view' | '
|
|
22
|
-
let showClearConfirm = $state(false);
|
|
18
|
+
let activeTab = $state<'view' | 'manage'>('view');
|
|
23
19
|
|
|
24
20
|
// Handle data refresh after import operations
|
|
25
21
|
function handleDataRefresh() {
|
|
26
22
|
onDataRefresh?.();
|
|
27
23
|
}
|
|
28
|
-
|
|
29
|
-
// Export data as JSON
|
|
30
|
-
async function handleExport() {
|
|
31
|
-
try {
|
|
32
|
-
await exportAntennas();
|
|
33
|
-
} catch (error) {
|
|
34
|
-
console.error('Export failed:', error);
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// Clear all data with confirmation
|
|
39
|
-
async function handleClearData() {
|
|
40
|
-
if (!showClearConfirm) {
|
|
41
|
-
showClearConfirm = true;
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
try {
|
|
46
|
-
await clearAllAntennas();
|
|
47
|
-
showClearConfirm = false;
|
|
48
|
-
handleDataRefresh();
|
|
49
|
-
} catch (error) {
|
|
50
|
-
console.error('Clear data failed:', error);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function cancelClear() {
|
|
55
|
-
showClearConfirm = false;
|
|
56
|
-
}
|
|
57
24
|
</script>
|
|
58
25
|
|
|
59
26
|
<!-- Modal -->
|
|
@@ -91,33 +58,12 @@
|
|
|
91
58
|
>
|
|
92
59
|
<i class="bi bi-table me-2"></i>View Data
|
|
93
60
|
</button>
|
|
94
|
-
<button
|
|
95
|
-
class="nav-link {activeTab === 'import' ? 'active' : ''}"
|
|
96
|
-
type="button"
|
|
97
|
-
onclick={() => activeTab = 'import'}
|
|
98
|
-
>
|
|
99
|
-
<i class="bi bi-upload me-2"></i>Import JSON
|
|
100
|
-
</button>
|
|
101
|
-
<button
|
|
102
|
-
class="nav-link {activeTab === 'msi' ? 'active' : ''}"
|
|
103
|
-
type="button"
|
|
104
|
-
onclick={() => activeTab = 'msi'}
|
|
105
|
-
>
|
|
106
|
-
<i class="bi bi-file-earmark-text me-2"></i>Import MSI
|
|
107
|
-
</button>
|
|
108
|
-
<button
|
|
109
|
-
class="nav-link {activeTab === 'export' ? 'active' : ''}"
|
|
110
|
-
type="button"
|
|
111
|
-
onclick={() => activeTab = 'export'}
|
|
112
|
-
>
|
|
113
|
-
<i class="bi bi-download me-2"></i>Export Data
|
|
114
|
-
</button>
|
|
115
61
|
<button
|
|
116
62
|
class="nav-link {activeTab === 'manage' ? 'active' : ''}"
|
|
117
63
|
type="button"
|
|
118
64
|
onclick={() => activeTab = 'manage'}
|
|
119
65
|
>
|
|
120
|
-
<i class="bi bi-
|
|
66
|
+
<i class="bi bi-database-gear me-2"></i>Manage Database
|
|
121
67
|
</button>
|
|
122
68
|
</div>
|
|
123
69
|
</div>
|
|
@@ -132,118 +78,10 @@
|
|
|
132
78
|
</div>
|
|
133
79
|
{/if}
|
|
134
80
|
|
|
135
|
-
<!--
|
|
136
|
-
{#if activeTab === 'import'}
|
|
137
|
-
<div class="tab-pane fade show active">
|
|
138
|
-
<div class="row">
|
|
139
|
-
<div class="col-12">
|
|
140
|
-
<h5 class="mb-3">Import Antenna Data from JSON</h5>
|
|
141
|
-
<JsonImporter onDataRefresh={handleDataRefresh} />
|
|
142
|
-
</div>
|
|
143
|
-
</div>
|
|
144
|
-
</div>
|
|
145
|
-
{/if}
|
|
146
|
-
|
|
147
|
-
<!-- Import MSI Tab -->
|
|
148
|
-
{#if activeTab === 'msi'}
|
|
149
|
-
<div class="tab-pane fade show active">
|
|
150
|
-
<div class="row">
|
|
151
|
-
<div class="col-12">
|
|
152
|
-
<h5 class="mb-3">Import MSI Pattern Files</h5>
|
|
153
|
-
<MSIConverter onDataRefresh={handleDataRefresh} {customBands} />
|
|
154
|
-
</div>
|
|
155
|
-
</div>
|
|
156
|
-
</div>
|
|
157
|
-
{/if}
|
|
158
|
-
|
|
159
|
-
<!-- Export Data Tab -->
|
|
160
|
-
{#if activeTab === 'export'}
|
|
161
|
-
<div class="tab-pane fade show active">
|
|
162
|
-
<div class="row">
|
|
163
|
-
<div class="col-12">
|
|
164
|
-
<h5 class="mb-3">Export Antenna Data</h5>
|
|
165
|
-
|
|
166
|
-
<div class="card border-success">
|
|
167
|
-
<div class="card-body text-center">
|
|
168
|
-
<i class="bi bi-download text-success" style="font-size: 3rem;"></i>
|
|
169
|
-
<h6 class="mt-3 mb-3">Download Data Backup</h6>
|
|
170
|
-
<p class="text-muted mb-4">
|
|
171
|
-
Export all antenna patterns and metadata as a JSON file.
|
|
172
|
-
</p>
|
|
173
|
-
<button
|
|
174
|
-
type="button"
|
|
175
|
-
class="btn btn-success btn-lg"
|
|
176
|
-
onclick={handleExport}
|
|
177
|
-
>
|
|
178
|
-
<i class="bi bi-download me-2"></i>
|
|
179
|
-
Export JSON File
|
|
180
|
-
</button>
|
|
181
|
-
</div>
|
|
182
|
-
</div>
|
|
183
|
-
</div>
|
|
184
|
-
</div>
|
|
185
|
-
</div>
|
|
186
|
-
{/if}
|
|
187
|
-
|
|
188
|
-
<!-- Manage Data Tab -->
|
|
81
|
+
<!-- Manage Database Tab -->
|
|
189
82
|
{#if activeTab === 'manage'}
|
|
190
83
|
<div class="tab-pane fade show active">
|
|
191
|
-
<
|
|
192
|
-
<div class="col-12">
|
|
193
|
-
<h5 class="mb-3">Manage Database</h5>
|
|
194
|
-
|
|
195
|
-
<!-- Database Status -->
|
|
196
|
-
<div class="card border-info mb-4">
|
|
197
|
-
<div class="card-body">
|
|
198
|
-
<h6 class="card-title">
|
|
199
|
-
<i class="bi bi-info-circle me-2"></i>Database Status
|
|
200
|
-
</h6>
|
|
201
|
-
<DbNotification />
|
|
202
|
-
</div>
|
|
203
|
-
</div>
|
|
204
|
-
|
|
205
|
-
<!-- Clear Data Section -->
|
|
206
|
-
<div class="card border-danger">
|
|
207
|
-
<div class="card-body">
|
|
208
|
-
<p class="text-muted mb-3">
|
|
209
|
-
Permanently delete all antenna data from the database.
|
|
210
|
-
</p>
|
|
211
|
-
|
|
212
|
-
{#if !showClearConfirm}
|
|
213
|
-
<button
|
|
214
|
-
type="button"
|
|
215
|
-
class="btn btn-outline-danger"
|
|
216
|
-
onclick={handleClearData}
|
|
217
|
-
>
|
|
218
|
-
<i class="bi bi-trash me-2"></i>
|
|
219
|
-
Clear All Data
|
|
220
|
-
</button>
|
|
221
|
-
{:else}
|
|
222
|
-
<div class="alert alert-danger" role="alert">
|
|
223
|
-
<strong>Are you sure?</strong> This will permanently delete all antenna data.
|
|
224
|
-
</div>
|
|
225
|
-
<div class="d-flex gap-2">
|
|
226
|
-
<button
|
|
227
|
-
type="button"
|
|
228
|
-
class="btn btn-danger"
|
|
229
|
-
onclick={handleClearData}
|
|
230
|
-
>
|
|
231
|
-
<i class="bi bi-trash me-2"></i>
|
|
232
|
-
Yes, Delete All Data
|
|
233
|
-
</button>
|
|
234
|
-
<button
|
|
235
|
-
type="button"
|
|
236
|
-
class="btn btn-secondary"
|
|
237
|
-
onclick={cancelClear}
|
|
238
|
-
>
|
|
239
|
-
Cancel
|
|
240
|
-
</button>
|
|
241
|
-
</div>
|
|
242
|
-
{/if}
|
|
243
|
-
</div>
|
|
244
|
-
</div>
|
|
245
|
-
</div>
|
|
246
|
-
</div>
|
|
84
|
+
<MSIConverter onDataRefresh={handleDataRefresh} {customBands} />
|
|
247
85
|
</div>
|
|
248
86
|
{/if}
|
|
249
87
|
|
|
@@ -296,12 +134,4 @@
|
|
|
296
134
|
border-bottom-color: #0d6efd;
|
|
297
135
|
background: none;
|
|
298
136
|
}
|
|
299
|
-
|
|
300
|
-
.card {
|
|
301
|
-
transition: box-shadow 0.15s ease-in-out;
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
.card:hover {
|
|
305
|
-
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
|
|
306
|
-
}
|
|
307
137
|
</style>
|
|
@@ -134,8 +134,8 @@
|
|
|
134
134
|
/>
|
|
135
135
|
</div>
|
|
136
136
|
<div class="col-md-5">
|
|
137
|
-
<
|
|
138
|
-
<div class="btn-group w-100 flex-wrap" role="group">
|
|
137
|
+
<span class="form-label d-block">Filter by Band</span>
|
|
138
|
+
<div class="btn-group w-100 flex-wrap" role="group" aria-label="Filter by frequency band">
|
|
139
139
|
<button
|
|
140
140
|
type="button"
|
|
141
141
|
class="btn btn-sm {selectedBand === null ? 'btn-primary' : 'btn-outline-primary'}"
|
|
@@ -1,9 +1,21 @@
|
|
|
1
1
|
<svelte:options runes={true} />
|
|
2
2
|
|
|
3
3
|
<script lang="ts">
|
|
4
|
+
import { untrack } from 'svelte';
|
|
5
|
+
import { SvelteSet } from 'svelte/reactivity';
|
|
4
6
|
import { parseFolderWithErrors, type ParseProgress, type ParseError } from '../utils/msi-parser';
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
+
import {
|
|
8
|
+
saveAntennasWithPurge,
|
|
9
|
+
addAntennas,
|
|
10
|
+
clearAllAntennas,
|
|
11
|
+
clearAntennasByBands,
|
|
12
|
+
getAntennasCountByBand,
|
|
13
|
+
exportAntennas,
|
|
14
|
+
loadAntennas,
|
|
15
|
+
type ImportResult
|
|
16
|
+
} from '../utils/db-utils';
|
|
17
|
+
import { purgeAntennas, calculatePurgeStats } from '../band-config';
|
|
18
|
+
import { STANDARD_BANDS } from '../types';
|
|
7
19
|
import type { RawAntenna, Antenna, BandDefinition } from '../types';
|
|
8
20
|
|
|
9
21
|
interface Props {
|
|
@@ -26,8 +38,25 @@
|
|
|
26
38
|
let parseErrors = $state<ParseError[]>([]);
|
|
27
39
|
let showErrorDetails = $state(false);
|
|
28
40
|
|
|
41
|
+
// Database management state
|
|
42
|
+
let bandCounts = $state<Map<number, number>>(new Map());
|
|
43
|
+
let selectedBandsForPurge = new SvelteSet<number>();
|
|
44
|
+
let showPurgeByBand = $state(false);
|
|
45
|
+
|
|
29
46
|
let recursiveScan = $state(true);
|
|
30
47
|
|
|
48
|
+
// Load band counts on mount
|
|
49
|
+
async function refreshBandCounts() {
|
|
50
|
+
bandCounts = await getAntennasCountByBand();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Refresh band counts when component mounts (run once)
|
|
54
|
+
$effect(() => {
|
|
55
|
+
untrack(() => {
|
|
56
|
+
refreshBandCounts();
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
31
60
|
async function selectFolder() {
|
|
32
61
|
try {
|
|
33
62
|
// Check if File System Access API is supported
|
|
@@ -96,14 +125,50 @@
|
|
|
96
125
|
isLoading = true;
|
|
97
126
|
message = 'Purging frequencies and saving to database...';
|
|
98
127
|
|
|
99
|
-
//
|
|
128
|
+
// Purge first
|
|
129
|
+
const purgedAntennas = purgeAntennas(rawAntennas, customBands);
|
|
130
|
+
const purgeStats = calculatePurgeStats(rawAntennas.length, purgedAntennas);
|
|
131
|
+
|
|
132
|
+
// Add to database (merge mode - won't clear existing)
|
|
133
|
+
const { added, updated } = await addAntennas(purgedAntennas);
|
|
134
|
+
|
|
135
|
+
importResult = {
|
|
136
|
+
success: true,
|
|
137
|
+
purgeStats
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
message = `Imported ${purgeStats.totalAfter} antennas (${added} new, ${updated} updated). ${purgeStats.reductionPercent}% reduction from ${purgeStats.totalBefore} raw files.`;
|
|
141
|
+
|
|
142
|
+
// Refresh band counts and notify parent
|
|
143
|
+
await refreshBandCounts();
|
|
144
|
+
onDataRefresh?.();
|
|
145
|
+
|
|
146
|
+
} catch (e) {
|
|
147
|
+
error = e instanceof Error ? e.message : 'Failed to save antennas to database';
|
|
148
|
+
} finally {
|
|
149
|
+
isLoading = false;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async function replaceDatabase() {
|
|
154
|
+
if (rawAntennas.length === 0) {
|
|
155
|
+
error = 'No antenna data to save.';
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
try {
|
|
160
|
+
isLoading = true;
|
|
161
|
+
message = 'Replacing database with new data...';
|
|
162
|
+
|
|
163
|
+
// Save with purge (clears existing first)
|
|
100
164
|
importResult = await saveAntennasWithPurge(rawAntennas, customBands);
|
|
101
165
|
|
|
102
166
|
if (importResult.success) {
|
|
103
167
|
const stats = importResult.purgeStats;
|
|
104
|
-
message = `
|
|
168
|
+
message = `Replaced database with ${stats.totalAfter} antennas (${stats.reductionPercent}% reduction from ${stats.totalBefore} raw files).`;
|
|
105
169
|
|
|
106
|
-
//
|
|
170
|
+
// Refresh band counts and notify parent
|
|
171
|
+
await refreshBandCounts();
|
|
107
172
|
onDataRefresh?.();
|
|
108
173
|
} else {
|
|
109
174
|
error = importResult.error || 'Failed to save antennas to database';
|
|
@@ -136,16 +201,125 @@
|
|
|
136
201
|
|
|
137
202
|
message = 'JSON file downloaded (raw data, not purged).';
|
|
138
203
|
}
|
|
204
|
+
|
|
205
|
+
async function handleExportDatabase() {
|
|
206
|
+
try {
|
|
207
|
+
isLoading = true;
|
|
208
|
+
message = 'Exporting database...';
|
|
209
|
+
await exportAntennas();
|
|
210
|
+
message = 'Database exported successfully.';
|
|
211
|
+
} catch (e) {
|
|
212
|
+
error = e instanceof Error ? e.message : 'Failed to export database';
|
|
213
|
+
} finally {
|
|
214
|
+
isLoading = false;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
async function handleImportJson(event: Event) {
|
|
219
|
+
const input = event.target as HTMLInputElement;
|
|
220
|
+
const file = input.files?.[0];
|
|
221
|
+
if (!file) return;
|
|
222
|
+
|
|
223
|
+
try {
|
|
224
|
+
isLoading = true;
|
|
225
|
+
message = 'Importing JSON file...';
|
|
226
|
+
|
|
227
|
+
const text = await file.text();
|
|
228
|
+
const data = JSON.parse(text) as Antenna[];
|
|
229
|
+
|
|
230
|
+
// Add to database (merge mode)
|
|
231
|
+
const { added, updated } = await addAntennas(data);
|
|
232
|
+
|
|
233
|
+
message = `Imported ${data.length} antennas (${added} new, ${updated} updated).`;
|
|
234
|
+
|
|
235
|
+
// Refresh band counts and notify parent
|
|
236
|
+
await refreshBandCounts();
|
|
237
|
+
onDataRefresh?.();
|
|
238
|
+
|
|
239
|
+
// Clear file input
|
|
240
|
+
input.value = '';
|
|
241
|
+
} catch (e) {
|
|
242
|
+
error = e instanceof Error ? e.message : 'Failed to import JSON file';
|
|
243
|
+
} finally {
|
|
244
|
+
isLoading = false;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
async function handlePurgeAll() {
|
|
249
|
+
if (!confirm('Are you sure you want to delete ALL antennas from the database?')) return;
|
|
250
|
+
|
|
251
|
+
try {
|
|
252
|
+
isLoading = true;
|
|
253
|
+
message = 'Clearing database...';
|
|
254
|
+
await clearAllAntennas();
|
|
255
|
+
message = 'Database cleared successfully.';
|
|
256
|
+
importResult = null;
|
|
257
|
+
rawAntennas = [];
|
|
258
|
+
|
|
259
|
+
// Refresh band counts and notify parent
|
|
260
|
+
await refreshBandCounts();
|
|
261
|
+
onDataRefresh?.();
|
|
262
|
+
} catch (e) {
|
|
263
|
+
error = e instanceof Error ? e.message : 'Failed to clear database';
|
|
264
|
+
} finally {
|
|
265
|
+
isLoading = false;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
async function handlePurgeByBands() {
|
|
270
|
+
if (selectedBandsForPurge.size === 0) {
|
|
271
|
+
error = 'Please select at least one band to purge.';
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const bandsArray = Array.from(selectedBandsForPurge);
|
|
276
|
+
if (!confirm(`Are you sure you want to delete all antennas in bands: ${bandsArray.join(', ')} MHz?`)) return;
|
|
277
|
+
|
|
278
|
+
try {
|
|
279
|
+
isLoading = true;
|
|
280
|
+
message = `Purging bands: ${bandsArray.join(', ')} MHz...`;
|
|
281
|
+
const deleted = await clearAntennasByBands(bandsArray);
|
|
282
|
+
message = `Deleted ${deleted} antennas from bands: ${bandsArray.join(', ')} MHz.`;
|
|
283
|
+
selectedBandsForPurge.clear();
|
|
284
|
+
showPurgeByBand = false;
|
|
285
|
+
|
|
286
|
+
// Refresh band counts and notify parent
|
|
287
|
+
await refreshBandCounts();
|
|
288
|
+
onDataRefresh?.();
|
|
289
|
+
} catch (e) {
|
|
290
|
+
error = e instanceof Error ? e.message : 'Failed to purge bands';
|
|
291
|
+
} finally {
|
|
292
|
+
isLoading = false;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function toggleBandSelection(band: number) {
|
|
297
|
+
if (selectedBandsForPurge.has(band)) {
|
|
298
|
+
selectedBandsForPurge.delete(band);
|
|
299
|
+
} else {
|
|
300
|
+
selectedBandsForPurge.add(band);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Calculate total antennas in database
|
|
305
|
+
let totalInDatabase = $derived(Array.from(bandCounts.values()).reduce((a, b) => a + b, 0));
|
|
139
306
|
</script>
|
|
140
307
|
|
|
141
308
|
<div class="card border-0 shadow-sm">
|
|
142
309
|
<div class="card-body">
|
|
143
310
|
<header class="d-flex flex-column flex-lg-row align-items-lg-center justify-content-between gap-3 mb-4">
|
|
144
311
|
<div>
|
|
145
|
-
<h1 class="h3 mb-2">
|
|
146
|
-
<p class="text-muted mb-0">
|
|
312
|
+
<h1 class="h3 mb-2">Antenna Database Management</h1>
|
|
313
|
+
<p class="text-muted mb-0">Import MSI/PNT files, export data, and manage frequency bands.</p>
|
|
314
|
+
</div>
|
|
315
|
+
<div class="d-flex align-items-center gap-2">
|
|
316
|
+
{#if totalInDatabase > 0}
|
|
317
|
+
<span class="badge text-bg-primary fs-6">{totalInDatabase} antennas</span>
|
|
318
|
+
{:else}
|
|
319
|
+
<span class="badge text-bg-secondary">Empty database</span>
|
|
320
|
+
{/if}
|
|
321
|
+
<span class="badge text-bg-success">Local processing</span>
|
|
147
322
|
</div>
|
|
148
|
-
<span class="badge text-bg-success">Local processing</span>
|
|
149
323
|
</header>
|
|
150
324
|
|
|
151
325
|
<!-- Bands Info -->
|
|
@@ -160,49 +334,134 @@
|
|
|
160
334
|
</div>
|
|
161
335
|
|
|
162
336
|
<div class="d-flex flex-column gap-4">
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
Processing…
|
|
168
|
-
{:else}
|
|
169
|
-
<i class="bi bi-folder-plus me-2"></i>
|
|
170
|
-
Choose folder with MSI / PNT files
|
|
171
|
-
{/if}
|
|
172
|
-
</button>
|
|
173
|
-
</div>
|
|
174
|
-
|
|
175
|
-
<div class="d-flex justify-content-center">
|
|
176
|
-
<div class="form-check form-switch">
|
|
177
|
-
<input class="form-check-input" type="checkbox" bind:checked={recursiveScan} id="recursiveScan" />
|
|
178
|
-
<label class="form-check-label" for="recursiveScan">
|
|
179
|
-
<i class="bi bi-arrow-down-circle me-1"></i>
|
|
180
|
-
Include sub-folders during scan
|
|
181
|
-
</label>
|
|
337
|
+
<!-- Import Section -->
|
|
338
|
+
<div class="card border">
|
|
339
|
+
<div class="card-header bg-light">
|
|
340
|
+
<i class="bi bi-folder-plus me-2"></i>Import from MSI/PNT Folder
|
|
182
341
|
</div>
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
<div class="col-md-6">
|
|
188
|
-
<button class="btn btn-success w-100" onclick={saveToDatabase} disabled={isLoading}>
|
|
189
|
-
{#if isLoading}
|
|
342
|
+
<div class="card-body">
|
|
343
|
+
<div class="text-center mb-3">
|
|
344
|
+
<button class="btn btn-primary btn-lg px-4" onclick={selectFolder} disabled={isLoading}>
|
|
345
|
+
{#if isLoading && parseProgress}
|
|
190
346
|
<span class="spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></span>
|
|
191
|
-
|
|
347
|
+
Processing…
|
|
192
348
|
{:else}
|
|
193
|
-
<i class="bi bi-
|
|
194
|
-
|
|
349
|
+
<i class="bi bi-folder-plus me-2"></i>
|
|
350
|
+
Choose folder with MSI / PNT files
|
|
195
351
|
{/if}
|
|
196
352
|
</button>
|
|
197
353
|
</div>
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
354
|
+
|
|
355
|
+
<div class="d-flex justify-content-center">
|
|
356
|
+
<div class="form-check form-switch">
|
|
357
|
+
<input class="form-check-input" type="checkbox" bind:checked={recursiveScan} id="recursiveScan" />
|
|
358
|
+
<label class="form-check-label" for="recursiveScan">
|
|
359
|
+
<i class="bi bi-arrow-down-circle me-1"></i>
|
|
360
|
+
Include sub-folders during scan
|
|
361
|
+
</label>
|
|
362
|
+
</div>
|
|
203
363
|
</div>
|
|
364
|
+
|
|
365
|
+
{#if rawAntennas.length > 0}
|
|
366
|
+
<hr>
|
|
367
|
+
<div class="row g-3">
|
|
368
|
+
<div class="col-md-4">
|
|
369
|
+
<button class="btn btn-success w-100" onclick={saveToDatabase} disabled={isLoading}>
|
|
370
|
+
<i class="bi bi-database-add me-2"></i>
|
|
371
|
+
Add to database
|
|
372
|
+
</button>
|
|
373
|
+
<small class="text-muted d-block text-center mt-1">Merge with existing</small>
|
|
374
|
+
</div>
|
|
375
|
+
<div class="col-md-4">
|
|
376
|
+
<button class="btn btn-warning w-100" onclick={replaceDatabase} disabled={isLoading}>
|
|
377
|
+
<i class="bi bi-arrow-repeat me-2"></i>
|
|
378
|
+
Replace database
|
|
379
|
+
</button>
|
|
380
|
+
<small class="text-muted d-block text-center mt-1">Clear first, then add</small>
|
|
381
|
+
</div>
|
|
382
|
+
<div class="col-md-4">
|
|
383
|
+
<button class="btn btn-outline-secondary w-100" onclick={downloadJson}>
|
|
384
|
+
<i class="bi bi-download me-2"></i>
|
|
385
|
+
Download raw JSON
|
|
386
|
+
</button>
|
|
387
|
+
<small class="text-muted d-block text-center mt-1">Before purge</small>
|
|
388
|
+
</div>
|
|
389
|
+
</div>
|
|
390
|
+
{/if}
|
|
204
391
|
</div>
|
|
205
|
-
|
|
392
|
+
</div>
|
|
393
|
+
|
|
394
|
+
<!-- Database Management Section -->
|
|
395
|
+
<div class="card border">
|
|
396
|
+
<div class="card-header bg-light d-flex justify-content-between align-items-center">
|
|
397
|
+
<span><i class="bi bi-database me-2"></i>Database Management</span>
|
|
398
|
+
</div>
|
|
399
|
+
<div class="card-body">
|
|
400
|
+
<div class="row g-3 mb-3">
|
|
401
|
+
<div class="col-md-4">
|
|
402
|
+
<button class="btn btn-outline-primary w-100" onclick={handleExportDatabase} disabled={isLoading || totalInDatabase === 0}>
|
|
403
|
+
<i class="bi bi-box-arrow-up me-2"></i>
|
|
404
|
+
Export to JSON
|
|
405
|
+
</button>
|
|
406
|
+
</div>
|
|
407
|
+
<div class="col-md-4">
|
|
408
|
+
<label class="btn btn-outline-primary w-100 mb-0" style="cursor: pointer;">
|
|
409
|
+
<i class="bi bi-box-arrow-in-down me-2"></i>
|
|
410
|
+
Import from JSON
|
|
411
|
+
<input type="file" accept=".json" onchange={handleImportJson} class="d-none" disabled={isLoading} />
|
|
412
|
+
</label>
|
|
413
|
+
</div>
|
|
414
|
+
<div class="col-md-4">
|
|
415
|
+
<button class="btn btn-outline-danger w-100" onclick={handlePurgeAll} disabled={isLoading || totalInDatabase === 0}>
|
|
416
|
+
<i class="bi bi-trash me-2"></i>
|
|
417
|
+
Purge All
|
|
418
|
+
</button>
|
|
419
|
+
</div>
|
|
420
|
+
</div>
|
|
421
|
+
|
|
422
|
+
<!-- Band-specific purge -->
|
|
423
|
+
{#if bandCounts.size > 0}
|
|
424
|
+
<div class="border rounded p-3 bg-light">
|
|
425
|
+
<div class="d-flex justify-content-between align-items-center mb-2">
|
|
426
|
+
<strong><i class="bi bi-layers me-2"></i>Antennas by Band</strong>
|
|
427
|
+
<button
|
|
428
|
+
class="btn btn-sm {showPurgeByBand ? 'btn-secondary' : 'btn-outline-secondary'}"
|
|
429
|
+
onclick={() => showPurgeByBand = !showPurgeByBand}
|
|
430
|
+
>
|
|
431
|
+
{showPurgeByBand ? 'Cancel' : 'Purge by Band'}
|
|
432
|
+
</button>
|
|
433
|
+
</div>
|
|
434
|
+
|
|
435
|
+
<div class="d-flex flex-wrap gap-2">
|
|
436
|
+
{#each Array.from(bandCounts.entries()).sort((a, b) => a[0] - b[0]) as [band, count]}
|
|
437
|
+
{#if showPurgeByBand}
|
|
438
|
+
<button
|
|
439
|
+
class="btn btn-sm {selectedBandsForPurge.has(band) ? 'btn-danger' : 'btn-outline-secondary'}"
|
|
440
|
+
onclick={() => toggleBandSelection(band)}
|
|
441
|
+
>
|
|
442
|
+
{band} MHz ({count})
|
|
443
|
+
{#if selectedBandsForPurge.has(band)}
|
|
444
|
+
<i class="bi bi-x-circle ms-1"></i>
|
|
445
|
+
{/if}
|
|
446
|
+
</button>
|
|
447
|
+
{:else}
|
|
448
|
+
<span class="badge text-bg-secondary">{band} MHz: {count}</span>
|
|
449
|
+
{/if}
|
|
450
|
+
{/each}
|
|
451
|
+
</div>
|
|
452
|
+
|
|
453
|
+
{#if showPurgeByBand && selectedBandsForPurge.size > 0}
|
|
454
|
+
<div class="mt-3">
|
|
455
|
+
<button class="btn btn-danger" onclick={handlePurgeByBands} disabled={isLoading}>
|
|
456
|
+
<i class="bi bi-trash me-2"></i>
|
|
457
|
+
Delete {selectedBandsForPurge.size} band(s)
|
|
458
|
+
</button>
|
|
459
|
+
</div>
|
|
460
|
+
{/if}
|
|
461
|
+
</div>
|
|
462
|
+
{/if}
|
|
463
|
+
</div>
|
|
464
|
+
</div>
|
|
206
465
|
|
|
207
466
|
<!-- Progress indicator during parsing -->
|
|
208
467
|
{#if isLoading && parseProgress}
|
|
@@ -11,6 +11,10 @@ class AntennaToolsDatabase extends Dexie {
|
|
|
11
11
|
this.version(1).stores({
|
|
12
12
|
antennas: '++id, name, frequency, originalFrequency, tilt'
|
|
13
13
|
});
|
|
14
|
+
// Version 2: Add compound index for duplicate detection
|
|
15
|
+
this.version(2).stores({
|
|
16
|
+
antennas: '++id, name, frequency, originalFrequency, tilt, [name+frequency+tilt]'
|
|
17
|
+
});
|
|
14
18
|
}
|
|
15
19
|
/**
|
|
16
20
|
* Check if database has any antenna data
|
|
@@ -45,6 +45,25 @@ export declare function exportAntennas(): Promise<void>;
|
|
|
45
45
|
* Clear all antenna data from database
|
|
46
46
|
*/
|
|
47
47
|
export declare function clearAllAntennas(): Promise<void>;
|
|
48
|
+
/**
|
|
49
|
+
* Clear antennas by specific frequency bands
|
|
50
|
+
* @param bands - Array of band frequencies to delete (e.g., [700, 800, 1800])
|
|
51
|
+
* @returns Number of antennas deleted
|
|
52
|
+
*/
|
|
53
|
+
export declare function clearAntennasByBands(bands: number[]): Promise<number>;
|
|
54
|
+
/**
|
|
55
|
+
* Get count of antennas per frequency band
|
|
56
|
+
* @returns Map of band frequency to count
|
|
57
|
+
*/
|
|
58
|
+
export declare function getAntennasCountByBand(): Promise<Map<number, number>>;
|
|
59
|
+
/**
|
|
60
|
+
* Add antennas to database (merge mode - no clearing)
|
|
61
|
+
* Duplicates (same name + frequency + tilt) are overwritten
|
|
62
|
+
*/
|
|
63
|
+
export declare function addAntennas(newAntennas: Antenna[]): Promise<{
|
|
64
|
+
added: number;
|
|
65
|
+
updated: number;
|
|
66
|
+
}>;
|
|
48
67
|
/**
|
|
49
68
|
* Check if data has been imported before
|
|
50
69
|
*/
|