const MARINE_API = API_URL + '/marine'; const MAPBOX_TOKEN = 'pk.eyJ1Ijoic2VzZWUzIiwiYSI6ImNtZ2dydndkMDBsNjUya3NjeW91dW41MzcifQ.M2qxj0wL1W7plRzIataojQ'; // ── State ────────────────────────────────────────────────────────────────── let selectedDatasetId = null; let selectedVariables = new Set(); let datasetDateRange = { min: null, max: null }; let tags = ['marine']; let currentBbox = null; let currentStep = 0; let map = null; let isDrawMode = false; let isDrawing = false; let drawStart = null; let pollInterval = null; const TOTAL_STEPS = 6; // Variable renames: { originalName: customName } let variableRenames = {}; let _currentRenaming = null; // ── Init ─────────────────────────────────────────────────────────────────── document.addEventListener('DOMContentLoaded', () => { initMap(); renderTags(); setupTagInput(); renderDots(); showStep(0, false); document.getElementById('catalogSearch').addEventListener('keydown', e => { if (e.key === 'Enter') searchCatalog(); }); document.getElementById('renameInput').addEventListener('keydown', e => { if (e.key === 'Enter') { e.preventDefault(); saveRename(); } if (e.key === 'Escape') closeRenameModal(); }); ['startDate','endDate','datasetName','outputFormat'].forEach(id => { const el = document.getElementById(id); if (el) el.addEventListener('change', () => { markDone(); updateSummary(); refreshNext(); }); }); }); // ── Stepper ──────────────────────────────────────────────────────────────── function showStep(i, smooth = true) { const steps = Array.from(document.querySelectorAll('.step')); currentStep = Math.max(0, Math.min(i, TOTAL_STEPS - 1)); steps.forEach((el, idx) => { const active = idx === currentStep; el.classList.toggle('active', active); if (active) el.removeAttribute('disabled'); else el.setAttribute('disabled', ''); }); document.getElementById('prevBtn').disabled = currentStep === 0; document.getElementById('nextBtn').textContent = currentStep === TOTAL_STEPS - 1 ? 'Fine' : 'Prossimo'; refreshNext(); updateDots(); updateSummary(); markDone(); if (smooth) { const active = steps[currentStep]; if (active) setTimeout(() => active.scrollIntoView({ behavior: 'smooth', block: 'center' }), 60); } } function refreshNext() { document.getElementById('nextBtn').disabled = !canAdvance(currentStep); } function nextStep() { if (!canAdvance(currentStep)) { showToast('Completa questo passo per continuare', 'error'); return; } if (currentStep < TOTAL_STEPS - 1) showStep(currentStep + 1); } function prevStep() { if (currentStep > 0) showStep(currentStep - 1); } function canAdvance(i) { switch (i) { case 0: return !!selectedDatasetId; case 1: return selectedVariables.size > 0; case 2: return !!currentBbox; case 3: { const s = document.getElementById('startDate').value; const e = document.getElementById('endDate').value; return !!(s && e && s <= e); } case 4: return !!document.getElementById('datasetName').value.trim(); default: return true; } } // ── Dots ─────────────────────────────────────────────────────────────────── function renderDots() { const c = document.getElementById('progressDots'); c.innerHTML = ''; for (let i = 0; i < TOTAL_STEPS; i++) { const d = document.createElement('button'); d.className = 'progress-dot'; d.setAttribute('aria-label', `Passo ${i + 1}`); d.addEventListener('click', () => { if (i <= currentStep) showStep(i); }); c.appendChild(d); } updateDots(); } function updateDots() { Array.from(document.getElementById('progressDots').children) .forEach((d, i) => d.classList.toggle('active', i === currentStep)); } // ── Done badges ──────────────────────────────────────────────────────────── function markDone() { const steps = document.querySelectorAll('.step'); const checks = [ !!selectedDatasetId, selectedVariables.size > 0, !!currentBbox, canAdvance(3), !!document.getElementById('datasetName').value.trim(), false, ]; steps.forEach((el, i) => el.classList.toggle('done', checks[i] === true)); } // ── Catalog ──────────────────────────────────────────────────────────────── async function searchCatalog() { const q = document.getElementById('catalogSearch').value.trim(); const btn = document.getElementById('searchBtn'); const box = document.getElementById('catalogResults'); box.innerHTML = '
Ricerca in corso...
'; btn.disabled = true; try { const params = q ? `?search=${encodeURIComponent(q)}&limit=30` : '?limit=30'; const res = await MEB_AUTH.fetch(`${MARINE_API}/catalog${params}`); const data = await res.json(); if (!res.ok) throw new Error(data.detail || 'Errore catalogo'); if (!data.datasets?.length) { box.innerHTML = '
Nessun dataset trovato
'; return; } box.innerHTML = ''; data.datasets.forEach(ds => { const item = document.createElement('div'); item.className = 'catalog-item'; item.innerHTML = `
${ds.dataset_id}
${ds.title || ''}
`; item.addEventListener('click', () => selectDataset(ds, item)); box.appendChild(item); }); } catch (e) { box.innerHTML = `
Errore: ${e.message}
`; } finally { btn.disabled = false; } } async function selectDataset(ds, itemEl) { document.querySelectorAll('.catalog-item').forEach(i => i.classList.remove('selected')); itemEl.classList.add('selected'); selectedDatasetId = ds.dataset_id; const badge = document.getElementById('selectedDsBadge'); badge.textContent = ds.dataset_id; badge.style.display = 'block'; // Reset dependent steps selectedVariables.clear(); const vBox = document.getElementById('variablesContainer'); vBox.innerHTML = 'Caricamento variabili...'; try { const res = await MEB_AUTH.fetch(`${MARINE_API}/catalog/${encodeURIComponent(ds.dataset_id)}`); const info = await res.json(); renderVariables(info.variables || ds.variables || []); if (info.min_longitude != null) { setBboxAndFit(info.min_longitude, info.max_longitude, info.min_latitude, info.max_latitude); } if (info.start_datetime) { prefillDates(info.start_datetime, info.end_datetime); } } catch { renderVariables(ds.variables || []); } markDone(); refreshNext(); // Auto-advance to variables step setTimeout(() => showStep(1), 700); } // ── Variables ────────────────────────────────────────────────────────────── function renderVariables(vars) { const c = document.getElementById('variablesContainer'); if (!vars?.length) { c.innerHTML = 'Nessuna variabile disponibile'; updateVarCount(); return; } c.innerHTML = ''; vars.forEach(v => { const name = typeof v === 'string' ? v : v.short_name; const desc = typeof v === 'object' ? (v.description || v.standard_name || '') : ''; const units = typeof v === 'object' && v.units ? v.units : ''; const chip = document.createElement('div'); chip.className = 'var-chip'; chip.dataset.name = name; chip.innerHTML = ` ${esc(name)} ${desc ? `${esc(desc)}` : ''} ${units ? `[${esc(units)}]` : ''} ${variableRenames[name] ? '→ ' + esc(variableRenames[name]) : ''} `; chip.addEventListener('click', () => toggleVar(chip, name)); c.appendChild(chip); }); updateVarCount(); } function toggleVar(chip, name) { if (selectedVariables.has(name)) { selectedVariables.delete(name); chip.classList.remove('selected'); } else { selectedVariables.add(name); chip.classList.add('selected'); } updateVarCount(); markDone(); refreshNext(); } function updateVarCount() { const n = selectedVariables.size; document.getElementById('varCount').textContent = n === 0 ? 'Nessuna selezionata' : `${n} selezionat${n === 1 ? 'a' : 'e'}`; } function selectAllVars() { document.querySelectorAll('.var-chip').forEach(chip => { chip.classList.add('selected'); selectedVariables.add(chip.dataset.name); }); updateVarCount(); markDone(); refreshNext(); } function deselectAllVars() { document.querySelectorAll('.var-chip').forEach(chip => chip.classList.remove('selected')); selectedVariables.clear(); updateVarCount(); markDone(); refreshNext(); } // ── Dates ────────────────────────────────────────────────────────────────── function prefillDates(minDate, maxDate) { datasetDateRange = { min: minDate, max: maxDate }; const s = document.getElementById('startDate'); const e = document.getElementById('endDate'); if (minDate) { s.min = minDate; s.value = minDate; e.min = minDate; } if (maxDate) { e.max = maxDate; e.value = maxDate; s.max = maxDate; } const hint = document.getElementById('dateRangeHint'); if (minDate && maxDate) hint.textContent = `Dati disponibili: ${minDate} → ${maxDate}`; markDone(); updateSummary(); } // ── Map ──────────────────────────────────────────────────────────────────── function initMap() { mapboxgl.accessToken = MAPBOX_TOKEN; map = new mapboxgl.Map({ container: 'mapContainer', style: 'mapbox://styles/mapbox/dark-v11', center: [14, 42], zoom: 3.5, attributionControl: false, }); map.addControl(new mapboxgl.NavigationControl({ showCompass: false }), 'top-right'); map.addControl(new mapboxgl.AttributionControl({ compact: true }), 'bottom-right'); map.on('load', () => { map.addSource('bbox-rect', { type: 'geojson', data: { type: 'FeatureCollection', features: [] } }); map.addLayer({ id: 'bbox-fill', type: 'fill', source: 'bbox-rect', paint: { 'fill-color': '#00a7f5', 'fill-opacity': 0.13 } }); map.addLayer({ id: 'bbox-line', type: 'line', source: 'bbox-rect', paint: { 'line-color': '#00a7f5', 'line-width': 2, 'line-dasharray': [4, 2] } }); }); map.getCanvas().addEventListener('mousedown', onCanvasMouseDown, true); window.addEventListener('mousemove', onWindowMouseMove); window.addEventListener('mouseup', onWindowMouseUp); } function startDraw() { if (!map) return; isDrawMode = true; map.dragPan.disable(); map.boxZoom.disable(); document.getElementById('mapContainer').classList.add('draw-mode'); const btn = document.getElementById('drawBtn'); btn.textContent = 'Clicca e trascina…'; btn.classList.replace('secondary', 'primary'); } function exitDrawMode() { isDrawMode = isDrawing = false; drawStart = null; map.dragPan.enable(); map.boxZoom.enable(); document.getElementById('mapContainer').classList.remove('draw-mode', 'drawing'); const btn = document.getElementById('drawBtn'); btn.textContent = 'Disegna area'; btn.classList.replace('primary', 'secondary'); } function onCanvasMouseDown(e) { if (!isDrawMode) return; e.preventDefault(); e.stopPropagation(); isDrawing = true; drawStart = map.unproject([e.offsetX, e.offsetY]); document.getElementById('mapContainer').classList.add('drawing'); } function onWindowMouseMove(e) { if (!isDrawing || !drawStart) return; const rc = map.getCanvas().getBoundingClientRect(); _drawRect(drawStart, map.unproject([e.clientX - rc.left, e.clientY - rc.top])); } function onWindowMouseUp(e) { if (!isDrawing || !drawStart) return; const rc = map.getCanvas().getBoundingClientRect(); const end = map.unproject([e.clientX - rc.left, e.clientY - rc.top]); setBbox( Math.min(drawStart.lng, end.lng), Math.max(drawStart.lng, end.lng), Math.min(drawStart.lat, end.lat), Math.max(drawStart.lat, end.lat) ); exitDrawMode(); } function _drawRect(a, b) { if (!map.getSource('bbox-rect')) return; map.getSource('bbox-rect').setData({ type: 'Feature', geometry: { type: 'Polygon', coordinates: [[[a.lng,a.lat],[b.lng,a.lat],[b.lng,b.lat],[a.lng,b.lat],[a.lng,a.lat]]] }, }); } function setBbox(minLon, maxLon, minLat, maxLat) { currentBbox = { minLon, maxLon, minLat, maxLat }; document.getElementById('minLon').value = minLon.toFixed(4); document.getElementById('maxLon').value = maxLon.toFixed(4); document.getElementById('minLat').value = minLat.toFixed(4); document.getElementById('maxLat').value = maxLat.toFixed(4); document.getElementById('bboxReadout').textContent = `${minLon.toFixed(2)}°/${minLat.toFixed(2)}° → ${maxLon.toFixed(2)}°/${maxLat.toFixed(2)}°`; _drawRect({ lng: minLon, lat: minLat }, { lng: maxLon, lat: maxLat }); markDone(); refreshNext(); } function setBboxAndFit(minLon, maxLon, minLat, maxLat) { const doIt = () => { setBbox(minLon, maxLon, minLat, maxLat); map.fitBounds([[minLon, minLat], [maxLon, maxLat]], { padding: 40, maxZoom: 10, duration: 600 }); }; if (!map) return; if (map.isStyleLoaded()) doIt(); else map.once('load', doIt); } function clearBbox() { currentBbox = null; ['minLon','maxLon','minLat','maxLat'].forEach(id => document.getElementById(id).value = ''); document.getElementById('bboxReadout').textContent = ''; if (map?.getSource('bbox-rect')) map.getSource('bbox-rect').setData({ type: 'FeatureCollection', features: [] }); markDone(); refreshNext(); } // ── Tags ─────────────────────────────────────────────────────────────────── function setupTagInput() { const inp = document.getElementById('tagInput'); inp.addEventListener('keydown', e => { if ((e.key === 'Enter' || e.key === ',') && inp.value.trim()) { e.preventDefault(); const t = inp.value.trim().replace(/,/g,'').toLowerCase(); if (t && !tags.includes(t)) { tags.push(t); renderTags(); } inp.value = ''; } else if (e.key === 'Backspace' && !inp.value && tags.length) { tags.pop(); renderTags(); } }); } function renderTags() { const wrap = document.getElementById('tagsWrap'); const inp = document.getElementById('tagInput'); wrap.innerHTML = ''; tags.forEach(t => { const chip = document.createElement('span'); chip.className = 'tag-chip'; chip.innerHTML = `${t} ×`; wrap.appendChild(chip); }); wrap.appendChild(inp); } function removeTag(t) { tags = tags.filter(x => x !== t); renderTags(); } // ── Download ─────────────────────────────────────────────────────────────── async function startDownload() { if (!selectedDatasetId) return showToast('Seleziona un dataset', 'error'); if (!selectedVariables.size) return showToast('Seleziona almeno una variabile', 'error'); if (!currentBbox) return showToast("Disegna un'area sulla mappa", 'error'); if (!document.getElementById('startDate').value || !document.getElementById('endDate').value) return showToast('Inserisci le date', 'error'); if (!document.getElementById('datasetName').value.trim()) return showToast('Inserisci un nome', 'error'); const body = { dataset_id: selectedDatasetId, variables: Array.from(selectedVariables), min_longitude: parseFloat(document.getElementById('minLon').value), max_longitude: parseFloat(document.getElementById('maxLon').value), min_latitude: parseFloat(document.getElementById('minLat').value), max_latitude: parseFloat(document.getElementById('maxLat').value), start_date: document.getElementById('startDate').value, end_date: document.getElementById('endDate').value, format: document.getElementById('outputFormat').value, nome: document.getElementById('datasetName').value.trim(), tags: [...tags], notes: document.getElementById('datasetNotes').value.trim(), variable_renames: { ...variableRenames }, }; const btn = document.getElementById('downloadBtn'); const prog = document.getElementById('downloadProgress'); btn.disabled = true; prog.style.display = 'block'; setProgress(0, 'Avvio download...'); try { const res = await MEB_AUTH.fetch(`${MARINE_API}/jobs`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body), }); const data = await res.json(); if (!res.ok) throw new Error(data.detail || 'Errore avvio job'); pollJob(data.job_id, btn, prog); } catch (e) { showToast(`Errore: ${e.message}`, 'error'); btn.disabled = false; prog.style.display = 'none'; } } function pollJob(jobId, btn, prog) { clearInterval(pollInterval); pollInterval = setInterval(async () => { try { const res = await MEB_AUTH.fetch(`${MARINE_API}/jobs/${jobId}`); const job = await res.json(); setProgress(job.progress, job.message); if (job.status === 'done') { clearInterval(pollInterval); btn.disabled = false; showToast('Dataset salvato con successo!', 'success'); setTimeout(resetAll, 1800); } else if (job.status === 'error') { clearInterval(pollInterval); btn.disabled = false; showToast(`Errore: ${job.message}`, 'error'); prog.style.display = 'none'; } } catch { /* transient */ } }, 2000); } function setProgress(pct, msg) { document.getElementById('progressFill').style.width = pct + '%'; document.getElementById('progressMsg').textContent = msg; } // ── Reset ────────────────────────────────────────────────────────────────── function resetAll() { selectedDatasetId = null; selectedVariables.clear(); datasetDateRange = { min: null, max: null }; variableRenames = {}; tags = ['marine']; ['startDate','endDate','datasetName','datasetNotes'].forEach(id => { const el = document.getElementById(id); if (el) { el.value = ''; el.min = ''; el.max = ''; } }); document.getElementById('catalogResults').innerHTML = '
Cerca un dataset Copernicus per iniziare
'; document.getElementById('selectedDsBadge').style.display = 'none'; document.getElementById('variablesContainer').innerHTML = 'Seleziona un dataset per vedere le variabili'; document.getElementById('dateRangeHint').textContent = ''; document.getElementById('downloadProgress').style.display = 'none'; clearBbox(); renderTags(); showStep(0); } // ── Summary ──────────────────────────────────────────────────────────────── function updateSummary() { if (currentStep !== 5) return; const s = document.getElementById('startDate').value || '-'; const e = document.getElementById('endDate').value || '-'; const name = document.getElementById('datasetName').value || '-'; const fmt = document.getElementById('outputFormat').value || '-'; const vars = Array.from(selectedVariables).join(', ') || '-'; const bbox = currentBbox ? `${currentBbox.minLon.toFixed(2)},${currentBbox.minLat.toFixed(2)} → ${currentBbox.maxLon.toFixed(2)},${currentBbox.maxLat.toFixed(2)}` : '-'; document.getElementById('summaryContent').innerHTML = `
Dataset: ${esc(selectedDatasetId || '-')}
Variabili (${selectedVariables.size}): ${esc(vars)}
Area: ${esc(bbox)}
Periodo: ${esc(s)} → ${esc(e)}
Formato: ${esc(fmt)}
Nome: ${esc(name)}
Tags: ${esc(tags.join(', '))}
`; } // ── Rename modal ─────────────────────────────────────────────────────────── function openRenameModal(varName) { _currentRenaming = varName; document.getElementById('renameVarLabel').textContent = varName; document.getElementById('renameInput').value = variableRenames[varName] || ''; document.getElementById('renameDeleteBtn').style.display = variableRenames[varName] ? 'inline-flex' : 'none'; document.getElementById('renameModal').classList.add('visible'); setTimeout(() => document.getElementById('renameInput').select(), 50); } function saveRename() { if (!_currentRenaming) return; const val = document.getElementById('renameInput').value.trim(); if (!val) { deleteRename(); return; } variableRenames[_currentRenaming] = val; _updateRenameBadge(_currentRenaming, val); closeRenameModal(); } function deleteRename() { if (!_currentRenaming) return; delete variableRenames[_currentRenaming]; _updateRenameBadge(_currentRenaming, ''); closeRenameModal(); } function closeRenameModal() { document.getElementById('renameModal').classList.remove('visible'); _currentRenaming = null; } function _updateRenameBadge(varName, text) { const chip = [...document.querySelectorAll('.var-chip')].find(c => c.dataset.name === varName); if (!chip) return; chip.querySelector('.rename-badge').textContent = text ? '→ ' + text : ''; } // ── Helpers ──────────────────────────────────────────────────────────────── function esc(s) { return String(s).replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"'); } function showToast(msg, type = 'success') { const t = document.getElementById('toast'); t.className = `${type} show`; t.textContent = msg; setTimeout(() => t.classList.remove('show'), 3500); }