Spaces:
Sleeping
Sleeping
| function uploadWithProgress(url, formData, onProgress) { | |
| return new Promise((resolve, reject) => { | |
| const xhr = new XMLHttpRequest(); | |
| xhr.open('POST', url); | |
| xhr.withCredentials = true; | |
| xhr.upload.addEventListener('progress', (e) => { | |
| if (e.lengthComputable && onProgress) onProgress(e.loaded, e.total); | |
| }); | |
| xhr.addEventListener('load', () => { | |
| let data = null; | |
| try { data = xhr.responseText ? JSON.parse(xhr.responseText) : null; } catch (_) {} | |
| if (xhr.status >= 200 && xhr.status < 300) resolve(data); | |
| else reject(new Error(data?.detail || xhr.statusText || 'Upload failed')); | |
| }); | |
| xhr.addEventListener('error', () => reject(new Error('Network error during upload'))); | |
| xhr.send(formData); | |
| }); | |
| } | |
| document.getElementById('form-tree-upload')?.addEventListener('submit', async (e) => { | |
| e.preventDefault(); | |
| hideDdaError?.(); | |
| const nodeId = document.getElementById('upload-node')?.value; | |
| const fileInput = document.getElementById('upload-file'); | |
| const file = fileInput?.files?.[0]; | |
| if (!nodeId) return showDdaError?.('Select a tree node.'); | |
| if (!file) return showDdaError?.('Select a file.'); | |
| const ext = (file.name.split('.').pop() || '').toLowerCase(); | |
| const isTiff = ext === 'tif' || ext === 'tiff'; | |
| const cfg = window.ddaState?.localCfg || {}; | |
| const maxBytes = isTiff | |
| ? (cfg.maxGeotiffBytes || (cfg.maxGeotiffMb || 15360) * 1024 * 1024) | |
| : (cfg.maxImageBytes || (cfg.maxImageMb || 15360) * 1024 * 1024); | |
| if (file.size > maxBytes) { | |
| return showDdaError?.(`File is ${formatBytes(file.size)} — max ${formatBytes(maxBytes)}.`); | |
| } | |
| const form = new FormData(); | |
| form.append('file', file); | |
| form.append('image_type', document.getElementById('upload-image-type')?.value || 'GeoTIFF'); | |
| form.append('capture_date', document.getElementById('upload-capture-date')?.value || ''); | |
| const manualBounds = document.getElementById('upload-manual-bounds')?.value?.trim(); | |
| if (manualBounds) form.append('manual_bounds', manualBounds); | |
| const btn = document.getElementById('btn-tree-upload'); | |
| const progWrap = document.getElementById('upload-progress'); | |
| const progFill = document.getElementById('upload-progress-fill'); | |
| const progLabel = document.getElementById('upload-progress-label'); | |
| btn.disabled = true; | |
| progWrap?.classList.remove('hidden'); | |
| if (progFill) progFill.style.width = '0%'; | |
| try { | |
| await uploadWithProgress(`/api/dda/tree/nodes/${nodeId}/images/upload`, form, (loaded, total) => { | |
| const pct = total ? Math.round((loaded / total) * 100) : 0; | |
| if (progFill) progFill.style.width = pct + '%'; | |
| if (progLabel) progLabel.textContent = `Uploading… ${pct}%`; | |
| }); | |
| showDdaSuccess?.('Uploaded. Refreshing…'); | |
| fileInput.value = ''; | |
| await window.ddaState.rescan(); | |
| } catch (err) { | |
| showDdaError?.(err.message || 'Upload failed.'); | |
| } finally { | |
| btn.disabled = false; | |
| setTimeout(() => progWrap?.classList.add('hidden'), 2000); | |
| } | |
| }); | |