// GigLez - Upload Functionality let selectedFiles = []; let autoDetectedGPS = null; // Store GPS detected from filenames // Initialize upload functionality document.addEventListener('DOMContentLoaded', () => { setupDropZone(); setupFileInput(); }); // GPS Extraction from Filenames // Matches patterns like: 34.0478N_118.2349W_1650_test_raw.sub class GPSFilenameExtractor { constructor() { // Pattern 1: Decimal degrees with N/S/E/W (most common) this.PATTERN_NSEW = /(\d+\.?\d*)([NS])_(\d+\.?\d*)([EW])/i; // Pattern 2: lat/lon prefix format this.PATTERN_LATLON = /lat(-?\d+\.?\d+)lon(-?\d+\.?\d+)/i; // Pattern 3: Signed decimal degrees this.PATTERN_SIGNED = /(-?\d+\.?\d+)_(-?\d+\.?\d+)/; } extract(filename) { // Try each pattern let coords = this.tryNSEW(filename) || this.tryLatLon(filename) || this.trySigned(filename); return coords; } tryNSEW(filename) { const match = filename.match(this.PATTERN_NSEW); if (!match) return null; let lat = parseFloat(match[1]); let lon = parseFloat(match[3]); const latDir = match[2].toUpperCase(); const lonDir = match[4].toUpperCase(); // Apply direction (N=positive, S=negative, E=positive, W=negative) if (latDir === 'S') lat = -lat; if (lonDir === 'W') lon = -lon; if (!this.validateCoordinates(lat, lon)) return null; return { latitude: lat, longitude: lon, source: 'filename_nsew' }; } tryLatLon(filename) { const match = filename.match(this.PATTERN_LATLON); if (!match) return null; const lat = parseFloat(match[1]); const lon = parseFloat(match[2]); if (!this.validateCoordinates(lat, lon)) return null; return { latitude: lat, longitude: lon, source: 'filename_latlon' }; } trySigned(filename) { const match = filename.match(this.PATTERN_SIGNED); if (!match) return null; const lat = parseFloat(match[1]); const lon = parseFloat(match[2]); if (!this.validateCoordinates(lat, lon)) return null; return { latitude: lat, longitude: lon, source: 'filename_signed' }; } validateCoordinates(lat, lon) { return lat >= -90 && lat <= 90 && lon >= -180 && lon <= 180; } } const gpsExtractor = new GPSFilenameExtractor(); function setupDropZone() { const dropZone = document.getElementById('drop-zone'); dropZone.addEventListener('click', () => { document.getElementById('file-input').click(); }); dropZone.addEventListener('dragover', (e) => { e.preventDefault(); dropZone.classList.add('drag-over'); }); dropZone.addEventListener('dragleave', () => { dropZone.classList.remove('drag-over'); }); dropZone.addEventListener('drop', (e) => { e.preventDefault(); dropZone.classList.remove('drag-over'); const files = Array.from(e.dataTransfer.files).filter(f => f.name.endsWith('.sub')); addFiles(files); }); } function setupFileInput() { const fileInput = document.getElementById('file-input'); fileInput.addEventListener('change', (e) => { const files = Array.from(e.target.files); addFiles(files); }); } function addFiles(files) { if (files.length === 0) { alert('Please select .sub files'); return; } // Add new files selectedFiles.push(...files); // Try to extract GPS from first file with coordinates if (!autoDetectedGPS) { for (const file of files) { const coords = gpsExtractor.extract(file.name); if (coords) { autoDetectedGPS = coords; // Auto-populate GPS fields document.getElementById('default-lat').value = coords.latitude.toFixed(6); document.getElementById('default-lon').value = coords.longitude.toFixed(6); // Show notification showGPSDetectionNotification(file.name, coords); break; } } } // Show file list document.getElementById('file-list').style.display = 'block'; // Render file list renderFileList(); } function showGPSDetectionNotification(filename, coords) { const notification = document.createElement('div'); notification.style.cssText = ` position: fixed; top: 20px; right: 20px; background: #10b981; color: white; padding: 1rem 1.5rem; border-radius: 0.5rem; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); z-index: 10000; max-width: 400px; animation: slideIn 0.3s ease-out; `; notification.innerHTML = ` 📍 GPS Auto-Detected!
From: ${filename}
Coordinates: ${coords.latitude.toFixed(6)}, ${coords.longitude.toFixed(6)} `; document.body.appendChild(notification); // Add animation const style = document.createElement('style'); style.textContent = ` @keyframes slideIn { from { transform: translateX(400px); opacity: 0; } to { transform: translateX(0); opacity: 1; } } `; document.head.appendChild(style); // Remove after 5 seconds setTimeout(() => { notification.style.animation = 'slideIn 0.3s ease-out reverse'; setTimeout(() => notification.remove(), 300); }, 5000); } function renderFileList() { const container = document.getElementById('files-container'); container.innerHTML = selectedFiles.map((file, index) => { const coords = gpsExtractor.extract(file.name); const hasGPS = coords !== null; return `
${file.name} ${hasGPS ? '📍 GPS' : ''}
${formatFileSize(file.size)} ${hasGPS ? ` | ${coords.latitude.toFixed(4)}, ${coords.longitude.toFixed(4)}` : ''}
`; }).join(''); } function removeFile(index) { selectedFiles.splice(index, 1); if (selectedFiles.length === 0) { document.getElementById('file-list').style.display = 'none'; } else { renderFileList(); } } function clearFiles() { selectedFiles = []; autoDetectedGPS = null; // Reset auto-detected GPS document.getElementById('file-list').style.display = 'none'; document.getElementById('file-input').value = ''; } async function uploadFiles() { if (selectedFiles.length === 0) { alert('Please select files to upload'); return; } // Validate GPS coordinates (allow auto-detected or manual entry) const lat = parseFloat(document.getElementById('default-lat').value); const lon = parseFloat(document.getElementById('default-lon').value); if (isNaN(lat) || isNaN(lon)) { // Check if any file has GPS in filename const hasGPSInFilename = selectedFiles.some(file => gpsExtractor.extract(file.name) !== null); if (hasGPSInFilename) { alert('GPS detected in filename but not populated. Please refresh and try again.'); } else { alert('Please provide GPS coordinates (manually or via filename like: 34.0478N_118.2349W_filename.sub)'); } return; } if (lat < -90 || lat > 90 || lon < -180 || lon > 180) { alert('GPS coordinates out of range'); return; } // Hide file list, show progress document.getElementById('file-list').style.display = 'none'; document.getElementById('upload-progress').style.display = 'block'; try { // Build manifest const sessionUuid = document.getElementById('session-uuid').value || generateUUID(); const accuracy = parseFloat(document.getElementById('default-accuracy').value) || 5.0; const altitude = parseFloat(document.getElementById('default-altitude').value) || null; const manifest = { session_uuid: sessionUuid, captures: selectedFiles.map(file => ({ filename: file.name, latitude: lat, longitude: lon, accuracy: accuracy, altitude: altitude, timestamp: new Date().toISOString() })) }; // Build FormData const formData = new FormData(); formData.append('manifest', JSON.stringify(manifest)); selectedFiles.forEach(file => { formData.append('files', file); }); // Upload updateProgress(0, 'Uploading files...'); const response = await fetch('/api/v1/captures/upload', { method: 'POST', body: formData }); updateProgress(100, 'Processing...'); if (!response.ok) { const error = await response.json(); throw new Error(error.detail || 'Upload failed'); } const result = await response.json(); // Show results showUploadResults(result); } catch (error) { console.error('Upload error:', error); alert(`Upload failed: ${error.message}`); // Reset document.getElementById('upload-progress').style.display = 'none'; document.getElementById('file-list').style.display = 'block'; } } function updateProgress(percent, text) { document.getElementById('progress-fill').style.width = `${percent}%`; document.getElementById('progress-text').textContent = text; } function showUploadResults(result) { // Hide progress document.getElementById('upload-progress').style.display = 'none'; // Show results const resultsDiv = document.getElementById('upload-results'); const container = document.getElementById('results-container'); resultsDiv.style.display = 'block'; const successCount = result.successful?.length || 0; const errorCount = result.failed?.length || 0; let html = `

Successfully uploaded: ${successCount} files

${errorCount > 0 ? `

Failed: ${errorCount} files

` : ''}
`; if (result.successful && result.successful.length > 0) { html += '

Successful Uploads:

'; result.successful.forEach(item => { html += `
${item.filename}
Frequency: ${(item.frequency / 1e6).toFixed(2)} MHz | Protocol: ${item.protocol || 'RAW'}
`; }); } if (result.failed && result.failed.length > 0) { html += '

Failed Uploads:

'; result.failed.forEach(item => { html += `
${item.filename}
Error: ${item.error}
`; }); } container.innerHTML = html; } function resetUpload() { // Hide results document.getElementById('upload-results').style.display = 'none'; // Clear files clearFiles(); // Reset form document.getElementById('default-lat').value = ''; document.getElementById('default-lon').value = ''; document.getElementById('session-uuid').value = ''; // Reset auto-detected GPS autoDetectedGPS = null; } function useCurrentLocation() { if (!navigator.geolocation) { alert('Geolocation is not supported by your browser'); return; } navigator.geolocation.getCurrentPosition( (position) => { document.getElementById('default-lat').value = position.coords.latitude.toFixed(6); document.getElementById('default-lon').value = position.coords.longitude.toFixed(6); document.getElementById('default-accuracy').value = position.coords.accuracy.toFixed(1); if (position.coords.altitude) { document.getElementById('default-altitude').value = position.coords.altitude.toFixed(1); } }, (error) => { alert(`Geolocation error: ${error.message}`); } ); } function generateUUID() { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { const r = Math.random() * 16 | 0; const v = c === 'x' ? r : (r & 0x3 | 0x8); return v.toString(16); }); } function formatFileSize(bytes) { if (bytes < 1024) return bytes + ' B'; if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB'; return (bytes / (1024 * 1024)).toFixed(1) + ' MB'; }