@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,266 @@
1
+ /**
2
+ * Antenna Tools - Database Utilities
3
+ * CRUD operations for antenna data with automatic purge on import
4
+ */
5
+ import { db } from '../db';
6
+ import { antennas } from '../stores/antennas';
7
+ import { updateDbStatus, trackDataOperation } from '../stores/db-status';
8
+ import { purgeAntennas, calculatePurgeStats } from '../band-config';
9
+ // ─────────────────────────────────────────────────────────────────────────────
10
+ // LOAD OPERATIONS
11
+ // ─────────────────────────────────────────────────────────────────────────────
12
+ /**
13
+ * Load all antennas from database
14
+ */
15
+ export async function loadAntennas() {
16
+ try {
17
+ updateDbStatus({ loading: true, error: null });
18
+ // Return empty array if db is not available (SSR environment)
19
+ if (!db) {
20
+ updateDbStatus({
21
+ initialized: false,
22
+ loading: false,
23
+ error: 'Database not available in this environment'
24
+ });
25
+ return [];
26
+ }
27
+ const data = await db.antennas.toArray();
28
+ antennas.set(data);
29
+ updateDbStatus({
30
+ initialized: true,
31
+ antennaCount: data.length,
32
+ lastUpdated: new Date(),
33
+ loading: false
34
+ });
35
+ return data;
36
+ }
37
+ catch (error) {
38
+ console.error('Error loading antennas from IndexedDB:', error);
39
+ updateDbStatus({
40
+ loading: false,
41
+ error: error instanceof Error ? error.message : 'Unknown database error'
42
+ });
43
+ return [];
44
+ }
45
+ }
46
+ /**
47
+ * Save raw antennas to database with automatic purge
48
+ *
49
+ * This is the main import function:
50
+ * 1. Takes raw antennas (with actual MHz frequencies)
51
+ * 2. Purges to keep only one per band (closest to center)
52
+ * 3. Saves purged antennas to database
53
+ *
54
+ * @param rawAntennas - Antennas parsed from MSI files
55
+ * @returns Import result with purge statistics
56
+ */
57
+ export async function saveAntennasWithPurge(rawAntennas) {
58
+ try {
59
+ trackDataOperation('import', { inProgress: true, message: 'Purging frequencies...' });
60
+ // Step 1: Purge - keep only one antenna per band
61
+ const purgedAntennas = purgeAntennas(rawAntennas);
62
+ const purgeStats = calculatePurgeStats(rawAntennas.length, purgedAntennas);
63
+ console.log(`[saveAntennasWithPurge] Purge complete: ${purgeStats.totalBefore} → ${purgeStats.totalAfter} antennas (${purgeStats.reductionPercent}% reduction)`);
64
+ trackDataOperation('import', { inProgress: true, message: `Saving ${purgedAntennas.length} antennas...` });
65
+ // Step 2: Clear existing data
66
+ await db.antennas.clear();
67
+ // Step 3: Bulk add the purged antennas
68
+ await db.antennas.bulkAdd(purgedAntennas);
69
+ // Update the store
70
+ antennas.set(purgedAntennas);
71
+ // Remember that we've imported data
72
+ localStorage.setItem('antenna-tools-data-imported', 'true');
73
+ // Update database status
74
+ updateDbStatus({
75
+ initialized: true,
76
+ antennaCount: purgedAntennas.length,
77
+ lastUpdated: new Date()
78
+ });
79
+ trackDataOperation('import', {
80
+ inProgress: false,
81
+ success: true,
82
+ message: `Imported ${purgedAntennas.length} antennas (${purgeStats.reductionPercent}% reduction)`
83
+ });
84
+ return { success: true, purgeStats };
85
+ }
86
+ catch (error) {
87
+ console.error('Error saving antennas to IndexedDB:', error);
88
+ trackDataOperation('import', {
89
+ inProgress: false,
90
+ success: false,
91
+ error: error instanceof Error ? error.message : 'Unknown error during import',
92
+ message: 'Import failed'
93
+ });
94
+ return {
95
+ success: false,
96
+ purgeStats: { totalBefore: rawAntennas.length, totalAfter: 0, removed: rawAntennas.length, reductionPercent: 100, perBand: {} },
97
+ error: error instanceof Error ? error.message : 'Unknown error'
98
+ };
99
+ }
100
+ }
101
+ /**
102
+ * Save antennas directly (without purge) - for pre-purged data
103
+ */
104
+ export async function saveAntennas(antennaData) {
105
+ try {
106
+ trackDataOperation('import', { inProgress: true, message: 'Saving antenna data...' });
107
+ // Clear existing data
108
+ await db.antennas.clear();
109
+ // Sanitize data to ensure it's cloneable for IndexedDB
110
+ const sanitizedData = antennaData.map(a => ({
111
+ name: a.name,
112
+ frequency: a.frequency,
113
+ originalFrequency: a.originalFrequency,
114
+ gain_dBd: a.gain_dBd,
115
+ tilt: a.tilt,
116
+ comment: a.comment,
117
+ horizontal: a.horizontal,
118
+ pattern: Array.isArray(a.pattern) ? [...a.pattern] : [],
119
+ vertical_pattern: Array.isArray(a.vertical_pattern) ? [...a.vertical_pattern] : [],
120
+ polarization: a.polarization,
121
+ x_pol: a.x_pol,
122
+ co: a.co,
123
+ tilt_from_filename: a.tilt_from_filename,
124
+ sourcePath: a.sourcePath,
125
+ }));
126
+ // Bulk add the new antennas
127
+ await db.antennas.bulkAdd(sanitizedData);
128
+ // Update the store
129
+ antennas.set(sanitizedData);
130
+ // Remember that we've imported data
131
+ localStorage.setItem('antenna-tools-data-imported', 'true');
132
+ // Update database status
133
+ updateDbStatus({
134
+ initialized: true,
135
+ antennaCount: sanitizedData.length,
136
+ lastUpdated: new Date()
137
+ });
138
+ trackDataOperation('import', {
139
+ inProgress: false,
140
+ success: true,
141
+ message: `Successfully imported ${sanitizedData.length} antennas`
142
+ });
143
+ }
144
+ catch (error) {
145
+ console.error('Error saving antennas to IndexedDB:', error);
146
+ trackDataOperation('import', {
147
+ inProgress: false,
148
+ success: false,
149
+ error: error instanceof Error ? error.message : 'Unknown error during import',
150
+ message: 'Import failed'
151
+ });
152
+ throw error;
153
+ }
154
+ }
155
+ // ─────────────────────────────────────────────────────────────────────────────
156
+ // IMPORT FROM FILES
157
+ // ─────────────────────────────────────────────────────────────────────────────
158
+ /**
159
+ * Import from JSON file (expects pre-purged data)
160
+ */
161
+ export async function importFromJson(jsonFile) {
162
+ try {
163
+ trackDataOperation('import', {
164
+ inProgress: true,
165
+ message: `Reading file: ${jsonFile.name}...`
166
+ });
167
+ const text = await jsonFile.text();
168
+ const data = JSON.parse(text);
169
+ trackDataOperation('import', {
170
+ inProgress: true,
171
+ message: `Importing ${data.length} antennas...`
172
+ });
173
+ await saveAntennas(data);
174
+ return data;
175
+ }
176
+ catch (error) {
177
+ console.error('Error importing antennas from JSON:', error);
178
+ trackDataOperation('import', {
179
+ inProgress: false,
180
+ success: false,
181
+ error: error instanceof Error ? error.message : 'Invalid JSON file or format',
182
+ message: 'Import failed'
183
+ });
184
+ throw error;
185
+ }
186
+ }
187
+ // ─────────────────────────────────────────────────────────────────────────────
188
+ // EXPORT OPERATIONS
189
+ // ─────────────────────────────────────────────────────────────────────────────
190
+ /**
191
+ * Export all antennas to JSON file download
192
+ */
193
+ export async function exportAntennas() {
194
+ try {
195
+ trackDataOperation('export', { inProgress: true, message: 'Preparing data export...' });
196
+ const data = await db.antennas.toArray();
197
+ const jsonData = JSON.stringify(data, null, 2);
198
+ // Create and download file
199
+ const blob = new Blob([jsonData], { type: 'application/json' });
200
+ const url = URL.createObjectURL(blob);
201
+ const link = document.createElement('a');
202
+ link.href = url;
203
+ link.download = `antenna-tools-${new Date().toISOString().split('T')[0]}.json`;
204
+ document.body.appendChild(link);
205
+ link.click();
206
+ document.body.removeChild(link);
207
+ URL.revokeObjectURL(url);
208
+ trackDataOperation('export', {
209
+ inProgress: false,
210
+ success: true,
211
+ message: `Successfully exported ${data.length} antennas`
212
+ });
213
+ }
214
+ catch (error) {
215
+ console.error('Error exporting antennas:', error);
216
+ trackDataOperation('export', {
217
+ inProgress: false,
218
+ success: false,
219
+ error: error instanceof Error ? error.message : 'Error exporting data',
220
+ message: 'Export failed'
221
+ });
222
+ throw error;
223
+ }
224
+ }
225
+ // ─────────────────────────────────────────────────────────────────────────────
226
+ // CLEAR OPERATIONS
227
+ // ─────────────────────────────────────────────────────────────────────────────
228
+ /**
229
+ * Clear all antenna data from database
230
+ */
231
+ export async function clearAllAntennas() {
232
+ try {
233
+ trackDataOperation('clear', { inProgress: true, message: 'Clearing database...' });
234
+ await db.antennas.clear();
235
+ antennas.set([]);
236
+ localStorage.removeItem('antenna-tools-data-imported');
237
+ updateDbStatus({
238
+ antennaCount: 0,
239
+ lastUpdated: new Date()
240
+ });
241
+ trackDataOperation('clear', {
242
+ inProgress: false,
243
+ success: true,
244
+ message: 'Database cleared successfully'
245
+ });
246
+ }
247
+ catch (error) {
248
+ console.error('Error clearing database:', error);
249
+ trackDataOperation('clear', {
250
+ inProgress: false,
251
+ success: false,
252
+ error: error instanceof Error ? error.message : 'Unknown error clearing database',
253
+ message: 'Failed to clear database'
254
+ });
255
+ throw error;
256
+ }
257
+ }
258
+ // ─────────────────────────────────────────────────────────────────────────────
259
+ // UTILITY FUNCTIONS
260
+ // ─────────────────────────────────────────────────────────────────────────────
261
+ /**
262
+ * Check if data has been imported before
263
+ */
264
+ export function hasImportedData() {
265
+ return localStorage.getItem('antenna-tools-data-imported') === 'true';
266
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Antenna Tools - Utils Index
3
+ */
4
+ export * from './msi-parser';
5
+ export * from './db-utils';
6
+ export * from './antenna-helpers';
7
+ export * from './recent-antennas';
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Antenna Tools - Utils Index
3
+ */
4
+ export * from './msi-parser';
5
+ export * from './db-utils';
6
+ export * from './antenna-helpers';
7
+ export * from './recent-antennas';
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Antenna Tools - MSI Parser
3
+ * Parses MSI antenna pattern files
4
+ *
5
+ * This is adapted from antenna-pattern/utils/msi-parser.ts
6
+ * Kept the same parsing logic as requested
7
+ */
8
+ import type { RawAntenna } from '../types';
9
+ /**
10
+ * Parse a single MSI file and return raw antenna data
11
+ * @param file - MSI or PNT file
12
+ * @returns Parsed antenna with actual MHz frequency
13
+ */
14
+ export declare function parseMSIFile(file: File): Promise<RawAntenna>;
15
+ /**
16
+ * Parse a folder of MSI files recursively
17
+ * @param directoryHandle - File system directory handle
18
+ * @param recursive - Whether to process subdirectories
19
+ * @returns Array of parsed antennas
20
+ */
21
+ export declare function parseFolder(directoryHandle: FileSystemDirectoryHandle, recursive?: boolean): Promise<RawAntenna[]>;
@@ -0,0 +1,215 @@
1
+ /**
2
+ * Antenna Tools - MSI Parser
3
+ * Parses MSI antenna pattern files
4
+ *
5
+ * This is adapted from antenna-pattern/utils/msi-parser.ts
6
+ * Kept the same parsing logic as requested
7
+ */
8
+ /**
9
+ * Parse a single MSI file and return raw antenna data
10
+ * @param file - MSI or PNT file
11
+ * @returns Parsed antenna with actual MHz frequency
12
+ */
13
+ export async function parseMSIFile(file) {
14
+ const text = await file.text();
15
+ const lines = text.split('\n');
16
+ // Initialize the result object with default values
17
+ const result = {
18
+ name: '',
19
+ frequency: 0,
20
+ gain_dBd: 0.0,
21
+ tilt: 'NO',
22
+ horizontal: 360,
23
+ pattern: Array(360).fill(0),
24
+ vertical_pattern: Array(360).fill(0)
25
+ };
26
+ // Parse filename for metadata
27
+ const filename = file.name.replace(/\.(msi|pnt)$/i, '');
28
+ const filenameParts = filename.split('_');
29
+ // Initialize filename metadata
30
+ const filenameMetadata = {
31
+ name_from_filename: '',
32
+ frequency_from_filename: 0,
33
+ x_pol: '',
34
+ co: '',
35
+ polarization: '',
36
+ tilt_from_filename: ''
37
+ };
38
+ if (filenameParts.length >= 5) {
39
+ filenameMetadata.name_from_filename = filenameParts[0];
40
+ // Extract frequency from filename (e.g., "0699" -> 699)
41
+ if (filenameParts.length > 1 && !isNaN(parseInt(filenameParts[1]))) {
42
+ filenameMetadata.frequency_from_filename = parseInt(filenameParts[1]);
43
+ }
44
+ // Extract X polarization
45
+ if (filenameParts.length > 2) {
46
+ filenameMetadata.x_pol = filenameParts[2];
47
+ }
48
+ // Extract CO
49
+ if (filenameParts.length > 3) {
50
+ filenameMetadata.co = filenameParts[3];
51
+ }
52
+ // Extract polarization (e.g., "M45" -> "-45")
53
+ if (filenameParts.length > 4 && filenameParts[4].startsWith('M')) {
54
+ filenameMetadata.polarization = filenameParts[4].replace('M', '-');
55
+ }
56
+ // Extract tilt (e.g., "03T" -> "03")
57
+ if (filenameParts.length > 5 && (filenameParts[5].endsWith('T') || filenameParts[5].endsWith('t'))) {
58
+ filenameMetadata.tilt_from_filename = filenameParts[5].replace(/[Tt]$/, '');
59
+ }
60
+ }
61
+ let currentSection = null;
62
+ // Process each line
63
+ for (const line of lines) {
64
+ const trimmedLine = line.trim();
65
+ // Skip empty lines
66
+ if (!trimmedLine)
67
+ continue;
68
+ // Check for metadata lines
69
+ if (trimmedLine.startsWith('NAME ')) {
70
+ if (!filenameMetadata.name_from_filename) {
71
+ result.name = trimmedLine.substring(5).trim();
72
+ }
73
+ else {
74
+ result.name = filenameMetadata.name_from_filename;
75
+ }
76
+ }
77
+ else if (trimmedLine.startsWith('FREQUENCY ')) {
78
+ if (!filenameMetadata.frequency_from_filename) {
79
+ const freqText = trimmedLine.substring(10).trim();
80
+ const freqParts = freqText.split(' ');
81
+ if (freqParts.length > 0) {
82
+ try {
83
+ result.frequency = parseInt(freqParts[0]);
84
+ }
85
+ catch (error) {
86
+ result.frequency = 0;
87
+ }
88
+ }
89
+ }
90
+ else {
91
+ result.frequency = filenameMetadata.frequency_from_filename;
92
+ }
93
+ }
94
+ else if (trimmedLine.startsWith('GAIN ')) {
95
+ const gainParts = trimmedLine.substring(5).trim().split(' ');
96
+ result.gain_dBd = parseFloat(gainParts[0]);
97
+ }
98
+ else if (trimmedLine.startsWith('TILT ')) {
99
+ if (!filenameMetadata.tilt_from_filename) {
100
+ result.tilt = trimmedLine.substring(5).trim();
101
+ }
102
+ else {
103
+ result.tilt = filenameMetadata.tilt_from_filename;
104
+ }
105
+ }
106
+ else if (trimmedLine.startsWith('COMMENT ')) {
107
+ result.comment = trimmedLine.substring(8).trim();
108
+ }
109
+ else if (trimmedLine.startsWith('HORIZONTAL ')) {
110
+ currentSection = 'horizontal';
111
+ result.horizontal = parseInt(trimmedLine.substring(11).trim());
112
+ }
113
+ else if (trimmedLine.startsWith('VERTICAL ')) {
114
+ currentSection = 'vertical';
115
+ }
116
+ // Parse pattern data lines (pattern values represent attenuation where 0 is max signal)
117
+ else if (currentSection === 'horizontal' && trimmedLine.includes(' ')) {
118
+ try {
119
+ const parts = trimmedLine.split(' ').filter(part => part.trim() !== '');
120
+ if (parts.length >= 2) {
121
+ const angleStr = parts[0];
122
+ const attenuationStr = parts[1];
123
+ const angle = parseInt(angleStr);
124
+ const attenuation = parseFloat(attenuationStr);
125
+ // Ensure the angle is within bounds
126
+ if (angle >= 0 && angle < 360) {
127
+ // Store as attenuation values (0 = max signal, higher values = more attenuation)
128
+ result.pattern[angle] = attenuation;
129
+ }
130
+ }
131
+ }
132
+ catch (error) {
133
+ // Skip lines that can't be parsed
134
+ }
135
+ }
136
+ else if (currentSection === 'vertical' && trimmedLine.includes(' ')) {
137
+ try {
138
+ const parts = trimmedLine.split(' ').filter(part => part.trim() !== '');
139
+ if (parts.length >= 2) {
140
+ const angleStr = parts[0];
141
+ const attenuationStr = parts[1];
142
+ const angle = parseInt(angleStr);
143
+ const attenuation = parseFloat(attenuationStr);
144
+ // Ensure the angle is within bounds
145
+ if (angle >= 0 && angle < 360) {
146
+ // Store as attenuation values (0 = max signal, higher values = more attenuation)
147
+ result.vertical_pattern[angle] = attenuation;
148
+ }
149
+ }
150
+ }
151
+ catch (error) {
152
+ // Skip lines that can't be parsed
153
+ }
154
+ }
155
+ }
156
+ // Ensure filename metadata is included in the final result
157
+ if (!result.name && filenameMetadata.name_from_filename) {
158
+ result.name = filenameMetadata.name_from_filename;
159
+ }
160
+ if (!result.frequency && filenameMetadata.frequency_from_filename) {
161
+ result.frequency = filenameMetadata.frequency_from_filename;
162
+ }
163
+ if (!result.tilt && filenameMetadata.tilt_from_filename) {
164
+ result.tilt = filenameMetadata.tilt_from_filename;
165
+ }
166
+ // Always include these fields from the filename
167
+ result.x_pol = filenameMetadata.x_pol;
168
+ result.co = filenameMetadata.co;
169
+ result.polarization = filenameMetadata.polarization;
170
+ return result;
171
+ }
172
+ /**
173
+ * Parse a folder of MSI files recursively
174
+ * @param directoryHandle - File system directory handle
175
+ * @param recursive - Whether to process subdirectories
176
+ * @returns Array of parsed antennas
177
+ */
178
+ export async function parseFolder(directoryHandle, recursive = true) {
179
+ const antennas = [];
180
+ // Process directory recursively
181
+ 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);
199
+ }
200
+ }
201
+ }
202
+ catch (error) {
203
+ console.error('Error accessing file:', error);
204
+ }
205
+ }
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
+ }
211
+ }
212
+ }
213
+ await processDirectory(directoryHandle);
214
+ return antennas;
215
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Antenna Tools - Recent Antennas
3
+ * Track recently selected antennas in localStorage
4
+ */
5
+ import type { Antenna } from '../types';
6
+ /**
7
+ * Get recently selected antennas from localStorage
8
+ */
9
+ export declare function getRecentAntennas(): Antenna[];
10
+ /**
11
+ * Add an antenna to the recent list
12
+ */
13
+ export declare function addToRecentAntennas(antenna: Antenna): void;
14
+ /**
15
+ * Clear recent antennas list
16
+ */
17
+ export declare function clearRecentAntennas(): void;
18
+ /**
19
+ * Sort antennas to show recent ones first, then alphabetical
20
+ */
21
+ export declare function sortAntennasWithRecent(antennas: Antenna[]): {
22
+ recent: Antenna[];
23
+ others: Antenna[];
24
+ };
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Antenna Tools - Recent Antennas
3
+ * Track recently selected antennas in localStorage
4
+ */
5
+ const RECENT_ANTENNAS_KEY = 'antenna-tools-recent';
6
+ const MAX_RECENT_ANTENNAS = 10;
7
+ /**
8
+ * Get recently selected antennas from localStorage
9
+ */
10
+ export function getRecentAntennas() {
11
+ try {
12
+ const stored = localStorage.getItem(RECENT_ANTENNAS_KEY);
13
+ if (!stored)
14
+ return [];
15
+ const recent = JSON.parse(stored);
16
+ return Array.isArray(recent) ? recent : [];
17
+ }
18
+ catch (error) {
19
+ console.warn('Failed to load recent antennas:', error);
20
+ return [];
21
+ }
22
+ }
23
+ /**
24
+ * Add an antenna to the recent list
25
+ */
26
+ export function addToRecentAntennas(antenna) {
27
+ try {
28
+ let recent = getRecentAntennas();
29
+ // Remove if already exists (to move to top)
30
+ recent = recent.filter(a => a.id !== antenna.id);
31
+ // Add to beginning
32
+ recent.unshift(antenna);
33
+ // Keep only the max number
34
+ recent = recent.slice(0, MAX_RECENT_ANTENNAS);
35
+ // Save to localStorage
36
+ localStorage.setItem(RECENT_ANTENNAS_KEY, JSON.stringify(recent));
37
+ }
38
+ catch (error) {
39
+ console.warn('Failed to save recent antenna:', error);
40
+ }
41
+ }
42
+ /**
43
+ * Clear recent antennas list
44
+ */
45
+ export function clearRecentAntennas() {
46
+ try {
47
+ localStorage.removeItem(RECENT_ANTENNAS_KEY);
48
+ }
49
+ catch (error) {
50
+ console.warn('Failed to clear recent antennas:', error);
51
+ }
52
+ }
53
+ /**
54
+ * Sort antennas to show recent ones first, then alphabetical
55
+ */
56
+ export function sortAntennasWithRecent(antennas) {
57
+ const recentAntennas = getRecentAntennas();
58
+ const recentIds = new Set(recentAntennas.map(a => a.id));
59
+ // Filter antennas that exist in current list and are recent
60
+ const recent = recentAntennas.filter(recentAntenna => antennas.some(antenna => antenna.id === recentAntenna.id));
61
+ // Get other antennas (not in recent list)
62
+ const others = antennas.filter(antenna => !recentIds.has(antenna.id));
63
+ return { recent, others };
64
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smartnet360/svelte-components",
3
- "version": "0.0.119",
3
+ "version": "0.0.120",
4
4
  "scripts": {
5
5
  "dev": "vite dev",
6
6
  "build": "vite build && npm run prepack",