@smartnet360/svelte-components 0.0.122 → 0.0.124

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 (28) hide show
  1. package/dist/apps/antenna-tools/band-config.d.ts +3 -2
  2. package/dist/apps/antenna-tools/band-config.js +6 -4
  3. package/dist/apps/antenna-tools/components/AntennaControls.svelte +71 -9
  4. package/dist/apps/antenna-tools/components/AntennaControls.svelte.d.ts +2 -0
  5. package/dist/apps/antenna-tools/components/AntennaSettingsModal.svelte +5 -2
  6. package/dist/apps/antenna-tools/components/AntennaSettingsModal.svelte.d.ts +3 -0
  7. package/dist/apps/antenna-tools/components/AntennaTools.svelte +55 -85
  8. package/dist/apps/antenna-tools/components/AntennaTools.svelte.d.ts +5 -3
  9. package/dist/apps/antenna-tools/components/DatabaseViewer.svelte +3 -6
  10. package/dist/apps/antenna-tools/components/MSIConverter.svelte +123 -15
  11. package/dist/apps/antenna-tools/components/MSIConverter.svelte.d.ts +3 -0
  12. package/dist/apps/antenna-tools/types.d.ts +8 -3
  13. package/dist/apps/antenna-tools/types.js +14 -8
  14. package/dist/apps/antenna-tools/utils/db-utils.d.ts +3 -2
  15. package/dist/apps/antenna-tools/utils/db-utils.js +3 -2
  16. package/dist/apps/antenna-tools/utils/msi-parser.d.ts +50 -2
  17. package/dist/apps/antenna-tools/utils/msi-parser.js +110 -27
  18. package/dist/apps/index.d.ts +2 -0
  19. package/dist/apps/index.js +1 -0
  20. package/dist/core/Benchmark/Benchmark.svelte +662 -0
  21. package/dist/core/Benchmark/Benchmark.svelte.d.ts +3 -0
  22. package/dist/core/Benchmark/benchmark-utils.d.ts +48 -0
  23. package/dist/core/Benchmark/benchmark-utils.js +80 -0
  24. package/dist/core/Benchmark/index.d.ts +2 -0
  25. package/dist/core/Benchmark/index.js +3 -0
  26. package/dist/core/index.d.ts +1 -0
  27. package/dist/core/index.js +2 -0
  28. package/package.json +1 -1
@@ -1,22 +1,30 @@
1
1
  <svelte:options runes={true} />
2
2
 
3
3
  <script lang="ts">
4
- import { parseFolder } from '../utils/msi-parser';
4
+ import { parseFolderWithErrors, type ParseProgress, type ParseError } from '../utils/msi-parser';
5
5
  import { saveAntennasWithPurge, type ImportResult } from '../utils/db-utils';
6
6
  import { STANDARD_BANDS } from '../band-config';
7
- import type { RawAntenna, Antenna } from '../types';
7
+ import type { RawAntenna, Antenna, BandDefinition } from '../types';
8
8
 
9
9
  interface Props {
10
10
  onDataRefresh?: () => void;
11
+ /** Custom band definitions for purge (defaults to STANDARD_BANDS) */
12
+ customBands?: BandDefinition[];
11
13
  }
12
14
 
13
- let { onDataRefresh }: Props = $props();
15
+ let { onDataRefresh, customBands }: Props = $props();
16
+
17
+ // Use custom bands if provided, otherwise use standard bands
18
+ let activeBands = $derived(customBands ?? STANDARD_BANDS);
14
19
 
15
20
  let rawAntennas = $state<RawAntenna[]>([]);
16
21
  let isLoading = $state(false);
17
22
  let error = $state('');
18
23
  let message = $state('');
19
24
  let importResult = $state<ImportResult | null>(null);
25
+ let parseProgress = $state<ParseProgress | null>(null);
26
+ let parseErrors = $state<ParseError[]>([]);
27
+ let showErrorDetails = $state(false);
20
28
 
21
29
  let recursiveScan = $state(true);
22
30
 
@@ -32,19 +40,32 @@
32
40
  error = '';
33
41
  message = 'Selecting folder...';
34
42
  importResult = null;
43
+ parseProgress = null;
44
+ parseErrors = [];
45
+ showErrorDetails = false;
35
46
 
36
47
  // Ask user to select a folder
37
48
  const directoryPicker = window.showDirectoryPicker as () => Promise<FileSystemDirectoryHandle>;
38
49
  const directoryHandle = await directoryPicker();
39
50
  message = recursiveScan ?
40
- 'Reading and parsing files recursively (including all subfolders)...' :
41
- 'Reading and parsing files (top-level folder only)...';
51
+ 'Scanning folders and parsing files...' :
52
+ 'Parsing files (top-level folder only)...';
53
+
54
+ // Parse all MSI/PNT files in the folder, with recursive option and progress
55
+ const result = await parseFolderWithErrors(directoryHandle, recursiveScan, (progress) => {
56
+ parseProgress = progress;
57
+ });
42
58
 
43
- // Parse all MSI/PNT files in the folder, with recursive option
44
- rawAntennas = await parseFolder(directoryHandle, recursiveScan);
59
+ rawAntennas = result.antennas;
60
+ parseErrors = result.errors;
45
61
 
46
- if (rawAntennas.length === 0) {
62
+ parseProgress = null;
63
+ if (rawAntennas.length === 0 && parseErrors.length === 0) {
47
64
  message = 'No MSI or PNT files found in the selected folder.';
65
+ } else if (rawAntennas.length === 0 && parseErrors.length > 0) {
66
+ message = `No files were successfully parsed. ${parseErrors.length} file(s) failed.`;
67
+ } else if (parseErrors.length > 0) {
68
+ message = `Parsed ${rawAntennas.length} antenna files${recursiveScan ? ' from all folders' : ''}. ${parseErrors.length} file(s) failed.`;
48
69
  } else {
49
70
  message = `Successfully parsed ${rawAntennas.length} antenna files${recursiveScan ? ' from all folders' : ''}.`;
50
71
  }
@@ -75,8 +96,8 @@
75
96
  isLoading = true;
76
97
  message = 'Purging frequencies and saving to database...';
77
98
 
78
- // Save with automatic purge
79
- importResult = await saveAntennasWithPurge(rawAntennas);
99
+ // Save with automatic purge (using custom bands if provided)
100
+ importResult = await saveAntennasWithPurge(rawAntennas, customBands);
80
101
 
81
102
  if (importResult.success) {
82
103
  const stats = importResult.purgeStats;
@@ -122,16 +143,16 @@
122
143
  <header class="d-flex flex-column flex-lg-row align-items-lg-center justify-content-between gap-3 mb-4">
123
144
  <div>
124
145
  <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>
146
+ <p class="text-muted mb-0">Scan a folder of antenna definitions. Files will be purged to keep only configured bands.</p>
126
147
  </div>
127
148
  <span class="badge text-bg-success">Local processing</span>
128
149
  </header>
129
150
 
130
- <!-- Standard Bands Info -->
151
+ <!-- Bands Info -->
131
152
  <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>
153
+ <strong><i class="bi bi-info-circle me-2"></i>{customBands ? 'Custom' : 'Standard'} Bands:</strong>
133
154
  <div class="d-flex flex-wrap gap-2 mt-2">
134
- {#each STANDARD_BANDS as band}
155
+ {#each activeBands as band}
135
156
  <span class="badge text-bg-primary">{band.name} MHz ({band.dlMin}-{band.dlMax})</span>
136
157
  {/each}
137
158
  </div>
@@ -183,13 +204,100 @@
183
204
  </div>
184
205
  {/if}
185
206
 
186
- {#if message && !error}
207
+ <!-- Progress indicator during parsing -->
208
+ {#if isLoading && parseProgress}
209
+ <div class="card border-info">
210
+ <div class="card-body">
211
+ <div class="d-flex align-items-center gap-3 mb-3">
212
+ <div class="spinner-border spinner-border-sm text-info" role="status">
213
+ <span class="visually-hidden">Loading...</span>
214
+ </div>
215
+ <span class="text-info fw-semibold">Parsing antenna files...</span>
216
+ </div>
217
+ <div class="row text-center mb-3">
218
+ <div class="col-3">
219
+ <div class="h4 mb-0 text-primary">{parseProgress.directoriesScanned}</div>
220
+ <small class="text-muted">Folders</small>
221
+ </div>
222
+ <div class="col-3">
223
+ <div class="h4 mb-0 text-info">{parseProgress.filesProcessed}</div>
224
+ <small class="text-muted">Processed</small>
225
+ </div>
226
+ <div class="col-3">
227
+ <div class="h4 mb-0 text-success">{parseProgress.antennaFilesFound}</div>
228
+ <small class="text-muted">Parsed</small>
229
+ </div>
230
+ <div class="col-3">
231
+ <div class="h4 mb-0 {parseProgress.failedFiles > 0 ? 'text-danger' : 'text-muted'}">{parseProgress.failedFiles}</div>
232
+ <small class="text-muted">Failed</small>
233
+ </div>
234
+ </div>
235
+ <div class="text-muted small text-truncate">
236
+ <i class="bi bi-file-earmark me-1"></i>
237
+ {parseProgress.currentFile}
238
+ </div>
239
+ </div>
240
+ </div>
241
+ {/if}
242
+
243
+ {#if message && !error && !parseProgress}
187
244
  <div class="alert alert-info border-0 bg-info-subtle text-info-emphasis" role="alert">
188
245
  <i class="bi bi-info-circle me-2"></i>
189
246
  {message}
190
247
  </div>
191
248
  {/if}
192
249
 
250
+ <!-- Parse Errors Summary -->
251
+ {#if parseErrors.length > 0 && !parseProgress}
252
+ <div class="alert alert-warning border-0 bg-warning-subtle" role="alert">
253
+ <div class="d-flex justify-content-between align-items-center">
254
+ <div>
255
+ <i class="bi bi-exclamation-triangle me-2"></i>
256
+ <strong>{parseErrors.length} file(s) failed to parse</strong>
257
+ <span class="text-muted ms-2">- Import continued with remaining files</span>
258
+ </div>
259
+ <button
260
+ class="btn btn-sm btn-outline-warning"
261
+ onclick={() => showErrorDetails = !showErrorDetails}
262
+ >
263
+ {showErrorDetails ? 'Hide' : 'Show'} details
264
+ </button>
265
+ </div>
266
+
267
+ {#if showErrorDetails}
268
+ <hr class="my-2">
269
+ <div class="small" style="max-height: 200px; overflow-y: auto;">
270
+ <table class="table table-sm table-borderless mb-0">
271
+ <thead>
272
+ <tr>
273
+ <th style="width: 60px;">Type</th>
274
+ <th>Path</th>
275
+ <th>Error</th>
276
+ </tr>
277
+ </thead>
278
+ <tbody>
279
+ {#each parseErrors as parseError}
280
+ <tr>
281
+ <td>
282
+ {#if parseError.type === 'folder'}
283
+ <span class="badge bg-secondary"><i class="bi bi-folder"></i></span>
284
+ {:else}
285
+ <span class="badge bg-danger"><i class="bi bi-file-earmark"></i></span>
286
+ {/if}
287
+ </td>
288
+ <td class="text-truncate" style="max-width: 300px;" title={parseError.path}>
289
+ {parseError.path}
290
+ </td>
291
+ <td class="text-muted">{parseError.message}</td>
292
+ </tr>
293
+ {/each}
294
+ </tbody>
295
+ </table>
296
+ </div>
297
+ {/if}
298
+ </div>
299
+ {/if}
300
+
193
301
  {#if error}
194
302
  <div class="alert alert-danger border-0 bg-danger-subtle text-danger-emphasis" role="alert">
195
303
  <i class="bi bi-exclamation-octagon me-2"></i>
@@ -1,5 +1,8 @@
1
+ import type { BandDefinition } from '../types';
1
2
  interface Props {
2
3
  onDataRefresh?: () => void;
4
+ /** Custom band definitions for purge (defaults to STANDARD_BANDS) */
5
+ customBands?: BandDefinition[];
3
6
  }
4
7
  declare const MsiConverter: import("svelte").Component<Props, {}, "">;
5
8
  type MsiConverter = ReturnType<typeof MsiConverter>;
@@ -6,7 +6,7 @@
6
6
  * Frequency band definition for filtering antennas
7
7
  */
8
8
  export interface BandDefinition {
9
- /** Simple band name: "700", "800", "900", "1800", "2100", "2600" */
9
+ /** Simple band name: "700", "800", "900", "1800", "2100", "2600", "3500" */
10
10
  name: string;
11
11
  /** Downlink minimum frequency (MHz) */
12
12
  dlMin: number;
@@ -14,8 +14,13 @@ export interface BandDefinition {
14
14
  dlMax: number;
15
15
  }
16
16
  /**
17
- * Standard frequency bands - fixed preset
18
- * Only downlink frequencies are considered
17
+ * Standard frequency bands with FULL downlink ranges per 3GPP standards
18
+ * These are the complete DL frequency ranges, not narrow slices
19
+ *
20
+ * References:
21
+ * - 3GPP TS 36.101 (LTE)
22
+ * - 3GPP TS 38.101 (5G NR)
23
+ * - ETSI standards for GSM
19
24
  */
20
25
  export declare const STANDARD_BANDS: BandDefinition[];
21
26
  /**
@@ -3,14 +3,20 @@
3
3
  * Clean, centralized type definitions for the antenna-tools module
4
4
  */
5
5
  /**
6
- * Standard frequency bands - fixed preset
7
- * Only downlink frequencies are considered
6
+ * Standard frequency bands with FULL downlink ranges per 3GPP standards
7
+ * These are the complete DL frequency ranges, not narrow slices
8
+ *
9
+ * References:
10
+ * - 3GPP TS 36.101 (LTE)
11
+ * - 3GPP TS 38.101 (5G NR)
12
+ * - ETSI standards for GSM
8
13
  */
9
14
  export const STANDARD_BANDS = [
10
- { name: '700', dlMin: 758, dlMax: 768 }, // LTE B28
11
- { name: '800', dlMin: 791, dlMax: 801 }, // LTE B20
12
- { name: '900', dlMin: 935, dlMax: 960 }, // GSM 900
13
- { name: '1800', dlMin: 1805, dlMax: 1880 }, // GSM 1800 / LTE B3
14
- { name: '2100', dlMin: 2110, dlMax: 2170 }, // LTE B1
15
- { name: '2600', dlMin: 2620, dlMax: 2690 }, // LTE B7
15
+ { name: '700', dlMin: 758, dlMax: 803 }, // LTE B28 (APT 700) - Full DL: 758-803 MHz
16
+ { name: '800', dlMin: 791, dlMax: 862 }, // LTE B20 (EU 800) - Full DL: 791-862 MHz
17
+ { name: '900', dlMin: 925, dlMax: 960 }, // GSM 900 / LTE B8 - Full DL: 925-960 MHz
18
+ { name: '1800', dlMin: 1805, dlMax: 1880 }, // GSM 1800 / LTE B3 - Full DL: 1805-1880 MHz
19
+ { name: '2100', dlMin: 2110, dlMax: 2170 }, // LTE B1 / UMTS - Full DL: 2110-2170 MHz
20
+ { name: '2600', dlMin: 2620, dlMax: 2690 }, // LTE B7 - Full DL: 2620-2690 MHz
21
+ { name: '3500', dlMin: 3400, dlMax: 3800 }, // 5G NR n78 (C-Band) - Full: 3400-3800 MHz
16
22
  ];
@@ -2,7 +2,7 @@
2
2
  * Antenna Tools - Database Utilities
3
3
  * CRUD operations for antenna data with automatic purge on import
4
4
  */
5
- import type { Antenna, RawAntenna } from '../types';
5
+ import type { Antenna, RawAntenna, BandDefinition } from '../types';
6
6
  import { type PurgeStats } from '../band-config';
7
7
  /**
8
8
  * Load all antennas from database
@@ -25,9 +25,10 @@ export interface ImportResult {
25
25
  * 3. Saves purged antennas to database
26
26
  *
27
27
  * @param rawAntennas - Antennas parsed from MSI files
28
+ * @param customBands - Optional custom band definitions (defaults to STANDARD_BANDS)
28
29
  * @returns Import result with purge statistics
29
30
  */
30
- export declare function saveAntennasWithPurge(rawAntennas: RawAntenna[]): Promise<ImportResult>;
31
+ export declare function saveAntennasWithPurge(rawAntennas: RawAntenna[], customBands?: BandDefinition[]): Promise<ImportResult>;
31
32
  /**
32
33
  * Save antennas directly (without purge) - for pre-purged data
33
34
  */
@@ -52,13 +52,14 @@ export async function loadAntennas() {
52
52
  * 3. Saves purged antennas to database
53
53
  *
54
54
  * @param rawAntennas - Antennas parsed from MSI files
55
+ * @param customBands - Optional custom band definitions (defaults to STANDARD_BANDS)
55
56
  * @returns Import result with purge statistics
56
57
  */
57
- export async function saveAntennasWithPurge(rawAntennas) {
58
+ export async function saveAntennasWithPurge(rawAntennas, customBands) {
58
59
  try {
59
60
  trackDataOperation('import', { inProgress: true, message: 'Purging frequencies...' });
60
61
  // Step 1: Purge - keep only one antenna per band
61
- const purgedAntennas = purgeAntennas(rawAntennas);
62
+ const purgedAntennas = purgeAntennas(rawAntennas, customBands);
62
63
  const purgeStats = calculatePurgeStats(rawAntennas.length, purgedAntennas);
63
64
  console.log(`[saveAntennasWithPurge] Purge complete: ${purgeStats.totalBefore} → ${purgeStats.totalAfter} antennas (${purgeStats.reductionPercent}% reduction)`);
64
65
  trackDataOperation('import', { inProgress: true, message: `Saving ${purgedAntennas.length} antennas...` });
@@ -12,10 +12,58 @@ import type { RawAntenna } from '../types';
12
12
  * @returns Parsed antenna with actual MHz frequency
13
13
  */
14
14
  export declare function parseMSIFile(file: File): Promise<RawAntenna>;
15
+ /**
16
+ * Progress information during folder parsing
17
+ */
18
+ export interface ParseProgress {
19
+ /** Current file being processed */
20
+ currentFile: string;
21
+ /** Number of files processed so far */
22
+ filesProcessed: number;
23
+ /** Number of directories scanned */
24
+ directoriesScanned: number;
25
+ /** Number of MSI/PNT files found */
26
+ antennaFilesFound: number;
27
+ /** Number of files that failed to parse */
28
+ failedFiles: number;
29
+ }
30
+ /**
31
+ * Error information for failed file/folder parsing
32
+ */
33
+ export interface ParseError {
34
+ /** Path to the file or folder that failed */
35
+ path: string;
36
+ /** Error message */
37
+ message: string;
38
+ /** Type of error */
39
+ type: 'file' | 'folder';
40
+ }
41
+ /**
42
+ * Result of folder parsing including errors
43
+ */
44
+ export interface ParseFolderResult {
45
+ /** Successfully parsed antennas */
46
+ antennas: RawAntenna[];
47
+ /** Errors encountered during parsing */
48
+ errors: ParseError[];
49
+ /** Total files attempted */
50
+ totalFilesAttempted: number;
51
+ /** Total directories scanned */
52
+ totalDirectoriesScanned: number;
53
+ }
15
54
  /**
16
55
  * Parse a folder of MSI files recursively
17
56
  * @param directoryHandle - File system directory handle
18
57
  * @param recursive - Whether to process subdirectories
19
- * @returns Array of parsed antennas
58
+ * @param onProgress - Optional callback for progress updates
59
+ * @returns Array of parsed antennas (for backward compatibility)
60
+ */
61
+ export declare function parseFolder(directoryHandle: FileSystemDirectoryHandle, recursive?: boolean, onProgress?: (progress: ParseProgress) => void): Promise<RawAntenna[]>;
62
+ /**
63
+ * Parse a folder of MSI files recursively with detailed error reporting
64
+ * @param directoryHandle - File system directory handle
65
+ * @param recursive - Whether to process subdirectories
66
+ * @param onProgress - Optional callback for progress updates
67
+ * @returns Object containing parsed antennas and any errors encountered
20
68
  */
21
- export declare function parseFolder(directoryHandle: FileSystemDirectoryHandle, recursive?: boolean): Promise<RawAntenna[]>;
69
+ export declare function parseFolderWithErrors(directoryHandle: FileSystemDirectoryHandle, recursive?: boolean, onProgress?: (progress: ParseProgress) => void): Promise<ParseFolderResult>;
@@ -173,43 +173,126 @@ export async function parseMSIFile(file) {
173
173
  * Parse a folder of MSI files recursively
174
174
  * @param directoryHandle - File system directory handle
175
175
  * @param recursive - Whether to process subdirectories
176
- * @returns Array of parsed antennas
176
+ * @param onProgress - Optional callback for progress updates
177
+ * @returns Array of parsed antennas (for backward compatibility)
177
178
  */
178
- export async function parseFolder(directoryHandle, recursive = true) {
179
+ export async function parseFolder(directoryHandle, recursive = true, onProgress) {
180
+ const result = await parseFolderWithErrors(directoryHandle, recursive, onProgress);
181
+ return result.antennas;
182
+ }
183
+ /**
184
+ * Parse a folder of MSI files recursively with detailed error reporting
185
+ * @param directoryHandle - File system directory handle
186
+ * @param recursive - Whether to process subdirectories
187
+ * @param onProgress - Optional callback for progress updates
188
+ * @returns Object containing parsed antennas and any errors encountered
189
+ */
190
+ export async function parseFolderWithErrors(directoryHandle, recursive = true, onProgress) {
179
191
  const antennas = [];
192
+ const errors = [];
193
+ let filesProcessed = 0;
194
+ let directoriesScanned = 0;
195
+ let failedFiles = 0;
180
196
  // Process directory recursively
181
197
  async function processDirectory(dirHandle, path = '') {
182
- const iterator = dirHandle.values();
183
- for await (const entry of iterator) {
184
- if (entry.kind === 'file') {
185
- try {
186
- // Type assertion to access getFile method
187
- const fileHandle = entry;
188
- const file = await fileHandle.getFile();
189
- const ext = file.name.split('.').pop()?.toLowerCase();
190
- if (ext === 'msi' || ext === 'pnt') {
191
- try {
192
- const antenna = await parseMSIFile(file);
193
- // Add the path info to help identify where the file was found
194
- antenna.sourcePath = path ? `${path}/${file.name}` : file.name;
195
- antennas.push(antenna);
196
- }
197
- catch (error) {
198
- console.error(`Error parsing file ${path ? `${path}/` : ''}${file.name}:`, error);
198
+ directoriesScanned++;
199
+ let iterator;
200
+ try {
201
+ iterator = dirHandle.values();
202
+ }
203
+ catch (error) {
204
+ const dirPath = path || dirHandle.name;
205
+ errors.push({
206
+ path: dirPath,
207
+ message: error instanceof Error ? error.message : 'Failed to read directory',
208
+ type: 'folder'
209
+ });
210
+ console.error(`Error reading directory ${dirPath}:`, error);
211
+ return; // Skip this directory but continue with others
212
+ }
213
+ try {
214
+ for await (const entry of iterator) {
215
+ if (entry.kind === 'file') {
216
+ try {
217
+ // Type assertion to access getFile method
218
+ const fileHandle = entry;
219
+ const file = await fileHandle.getFile();
220
+ const ext = file.name.split('.').pop()?.toLowerCase();
221
+ if (ext === 'msi' || ext === 'pnt') {
222
+ filesProcessed++;
223
+ const currentPath = path ? `${path}/${file.name}` : file.name;
224
+ // Report progress
225
+ onProgress?.({
226
+ currentFile: currentPath,
227
+ filesProcessed,
228
+ directoriesScanned,
229
+ antennaFilesFound: antennas.length,
230
+ failedFiles
231
+ });
232
+ try {
233
+ const antenna = await parseMSIFile(file);
234
+ // Add the path info to help identify where the file was found
235
+ antenna.sourcePath = currentPath;
236
+ antennas.push(antenna);
237
+ }
238
+ catch (parseError) {
239
+ failedFiles++;
240
+ errors.push({
241
+ path: currentPath,
242
+ message: parseError instanceof Error ? parseError.message : 'Failed to parse file',
243
+ type: 'file'
244
+ });
245
+ console.error(`Error parsing file ${currentPath}:`, parseError);
246
+ // Continue with next file
247
+ }
199
248
  }
200
249
  }
250
+ catch (fileError) {
251
+ const filePath = path ? `${path}/${entry.name}` : entry.name;
252
+ failedFiles++;
253
+ errors.push({
254
+ path: filePath,
255
+ message: fileError instanceof Error ? fileError.message : 'Failed to access file',
256
+ type: 'file'
257
+ });
258
+ console.error(`Error accessing file ${filePath}:`, fileError);
259
+ // Continue with next file
260
+ }
201
261
  }
202
- catch (error) {
203
- console.error('Error accessing file:', error);
262
+ else if (entry.kind === 'directory' && recursive) {
263
+ // Process subdirectory if recursive flag is true
264
+ const subDirPath = path ? `${path}/${entry.name}` : entry.name;
265
+ try {
266
+ await processDirectory(entry, subDirPath);
267
+ }
268
+ catch (subDirError) {
269
+ errors.push({
270
+ path: subDirPath,
271
+ message: subDirError instanceof Error ? subDirError.message : 'Failed to process subdirectory',
272
+ type: 'folder'
273
+ });
274
+ console.error(`Error processing subdirectory ${subDirPath}:`, subDirError);
275
+ // Continue with next entry
276
+ }
204
277
  }
205
278
  }
206
- else if (entry.kind === 'directory' && recursive) {
207
- // Process subdirectory if recursive flag is true
208
- const subDirPath = path ? `${path}/${entry.name}` : entry.name;
209
- await processDirectory(entry, subDirPath);
210
- }
279
+ }
280
+ catch (iterError) {
281
+ const dirPath = path || dirHandle.name;
282
+ errors.push({
283
+ path: dirPath,
284
+ message: iterError instanceof Error ? iterError.message : 'Error iterating directory contents',
285
+ type: 'folder'
286
+ });
287
+ console.error(`Error iterating directory ${dirPath}:`, iterError);
288
+ // Continue - the directory iteration failed but we can still return what we have
211
289
  }
212
290
  }
213
291
  await processDirectory(directoryHandle);
214
- return antennas;
292
+ return {
293
+ antennas,
294
+ errors,
295
+ totalFilesAttempted: filesProcessed,
296
+ totalDirectoriesScanned: directoriesScanned
297
+ };
215
298
  }
@@ -2,4 +2,6 @@ export * from './antenna-pattern/index.js';
2
2
  export { default as AntennaTools } from './antenna-tools/components/AntennaTools.svelte';
3
3
  export { default as AntennaDatabaseModal } from './antenna-tools/components/AntennaSettingsModal.svelte';
4
4
  export { default as AntennaDatabaseViewer } from './antenna-tools/components/DatabaseViewer.svelte';
5
+ export type { BandDefinition, Antenna, RawAntenna, ExternalAntennaInput } from './antenna-tools/types.js';
6
+ export { STANDARD_BANDS } from './antenna-tools/types.js';
5
7
  export * from './site-check/index.js';
@@ -7,6 +7,7 @@ export * from './antenna-pattern/index.js';
7
7
  export { default as AntennaTools } from './antenna-tools/components/AntennaTools.svelte';
8
8
  export { default as AntennaDatabaseModal } from './antenna-tools/components/AntennaSettingsModal.svelte';
9
9
  export { default as AntennaDatabaseViewer } from './antenna-tools/components/DatabaseViewer.svelte';
10
+ export { STANDARD_BANDS } from './antenna-tools/types.js';
10
11
  // Test app (if needed for demos)
11
12
  // export * from './test/index.js';
12
13
  export * from './site-check/index.js';