@@ -8,6 +8,10 @@ Parla con le rotte dello stesso servizio: /camera/sources, /camera/anteprima,
/camera/parametri, /camera/parametri/salva, /camera/calibration, /camera/calibrate.
#84: calibrazione densa, un solo dato dell ' operatore (lato dello scacco in mm).
#87: anteprima, parametri camera, stile TMF.
#91: due tab, «Acquisizione» e «Calibrazione», nella stessa pagina; l ' anteprima resta
visibile in entrambe. Calibrazioni salvate con nome in un archivio unico
(/camera/calibrazioni, /camera/calibrazione-attiva); il calcolo non salva piu ' :
si salva dal calcolo_id con un nome.
Dipende da: niente. E ' una stringa, cosi ' il pacchetto compilato non ha file dati.
"""
@@ -75,16 +79,45 @@ PAGINA = r"""<!doctype html>
.parametro .campo input[type=text] { width: 110px; text-align: right; flex: none; }
.parametro .unita { font-size: .82rem; color: var(--secondario); min-width: 2.2em; }
.parametro .limiti { font-size: .75rem; color: var(--secondario); }
/* #91: tab Acquisizione | Calibrazione */
.tabs { display: flex; gap: 6px; margin: 0 0 14px; border-bottom: 1px solid var(--bordo); }
.tabs button { background: transparent; color: var(--secondario); border-radius: 6px 6px 0 0; border-bottom: 3px solid transparent;
flex: 0 1 auto; padding: 8px 18px; }
.tabs button[aria-selected=true] { color: var(--testo); background: var(--pannello); border-bottom-color: var(--accento); font-weight: 600; }
/* #91: calibrazione con nome, elenco delle salvate */
.scorri { overflow-x: auto; margin-top: 8px; }
.elenco { width: 100 % ; }
.elenco th { text-align: left; font-size: .78rem; font-weight: 600; color: var(--secondario); padding: 2px 12px 4px 0; white-space: nowrap; }
.elenco td { padding: 5px 12px 5px 0; border-top: 1px solid var(--bordo); vertical-align: top; }
.elenco td:first-child { color: var(--testo); }
.elenco .azioni { white-space: nowrap; }
.elenco button { padding: 3px 9px; font-size: .82rem; margin-right: 4px; }
.segno { display: inline-block; font-size: .72rem; padding: 0 6px; border-radius: 9px; background: var(--ok); color: #052e16; margin-left: 6px; }
.segno.diversa { background: var(--avviso); color: #422006; }
.origine label { display: inline-block; margin: 0 14px 4px 0; color: var(--testo); font-size: .92rem; }
input[type=radio] { accent-color: var(--accento); }
@media (max-width: 900px) {
.griglia { grid-template-columns: minmax(0, 1fr); }
.cornice img { max-height: 60vh; }
}
@media (max-width: 500px) { /* #91: utilizzabile anche stretta (400 px) */
main { padding: 8px; }
.scheda { padding: 10px; }
.tabs button { flex: 1 1 0; padding: 8px 6px; }
.parametro .campo input[type=text] { width: 88px; }
}
</style>
</head>
<body>
<main>
<h1>Acquisizione camera</h1>
<!-- #91: due tab; la colonna dell ' anteprima resta visibile in entrambe -->
<div class= " tabs " role= " tablist " aria-label= " Sezioni della pagina " >
<button id= " tabAcq " type= " button " role= " tab " aria-selected= " true " aria-controls= " pannelloAcq " >Acquisizione</button>
<button id= " tabCal " type= " button " role= " tab " aria-selected= " false " aria-controls= " pannelloCal " tabindex= " -1 " >Calibrazione</button>
</div>
<div class= " griglia " >
<div class= " colonna " >
<!-- #87: sezione Anteprima -->
@@ -132,7 +165,7 @@ PAGINA = r"""<!doctype html>
</section>
</div>
<div class= " colonna " >
<div class= " colonna " id= " pannelloAcq " role= " tabpanel " aria-labelledby= " tabAcq " >
<!-- #87: sezione Sorgente -->
<section class= " scheda " id= " sezioneSorgente " >
<h2>Sorgente</h2>
@@ -151,43 +184,51 @@ PAGINA = r"""<!doctype html>
</div>
<div id= " msgParametri " class= " messaggio " ></div>
</section>
</div>
<!-- #91: tab Calibrazione (colonna destra al posto di Sorgente e Parametri) -->
<div class= " colonna " id= " pannelloCal " role= " tabpanel " aria-labelledby= " tabCal " hidden>
<section class= " scheda " id= " sezioneCalibrazione " >
<h2>Calibrazione</h2>
<div class= " riga " >
<div class= " stato " style= " margin:0 0 10px " >Sorgente: <strong id= " calSorgente " >-</strong> (si cambia nella tab Acquisizione)</div >
<div class= " origine " >
<label for= " origSorgente " ><input id= " origSorgente " name= " origine " type= " radio " value= " sorgente " checked> Sorgente selezionata</label>
<label for= " origFile " ><input id= " origFile " name= " origine " type= " radio " value= " file " > Immagine da file</label>
</div>
<div class= " riga " style= " margin-top:4px " >
<button id= " scegliFile " class= " secondario " >Scegli immagine...</button>
<input id= " file " type= " file " accept= " image/* " hidden>
<span id= " nomeFile " class= " nota " style= " margin:0 0 6px " >nessuna immagine scelta</span>
</div>
<div class= " riga " style= " margin-top:10px " >
<div><label for= " quadrato " >Lato dello scacco (mm)</label><input id= " quadrato " type= " text " inputmode= " decimal " value= " 2 " size= " 8 " ></div>
<button id= " acquisisci " title= " Acquisisci e calibra " >Calcola</button>
</div>
<p class= " nota " >
Servono una scacchiera che riempie tutto il campo della camera, piana, ferma e ben illuminata, e il lato di uno scacco.
La griglia si trova da sola; il risultato corregge anche la distorsione dell ' ottica.
Durante il calcolo l ' anteprima si sospende.</p>
<div class= " riga " style= " margin-top:10px " >
<button id= " acquisisci " >Acquisisci e calibra</button>
<button id= " scegliFile " class= " secondario " >Scegli immagine...</button>
<input id= " file " type= " file " accept= " image/* " hidden>
<span id= " nomeFile " class= " nota " style= " margin:0 0 6px " >nessuna immagine scelta</span>
<button id= " carica " class= " secondario " >Carica immagine e calibra</button>
</div>
Calcolando dalla sorgente l ' anteprima si sospende e poi riprende .</p>
<div id= " messaggio " class= " messaggio " ></div>
<div id= " risultato " hidden style= " margin-top:10px " >
<strong>Risultato del calcolo</strong>
<div id= " origineCalcolo " class= " nota " style= " margin:2px 0 6px " ></div>
<table id= " tabella " ></table>
<div id= " avviso " class= " avviso " ></div>
<div class= " riga " style= " margin-top:10px " >
<div><label for= " nomeCal " >Nome</label><input id= " nomeCal " type= " text " maxlength= " 60 " size= " 22 " autocomplete= " off " ></div>
<button id= " salva " >Salva</button>
<span class= " nota " style= " margin:0 0 6px " >Da camera, salvando si acquisisce e si calcola di nuovo: tenere la scacchiera ferma.</span>
</div>
<div id= " msgSalva " class= " messaggio " ></div>
<img id= " overlay " alt= " Angoli trovati sulla scacchiera " style= " max-width:100 % ;margin-top:8px;border:1px solid var(--bordo);border-radius:6px " >
</div>
</section>
<div style= " margin-top:14px " >
<strong >Calibrazione salvata </strong >
<div id= " stato " class= " stato " >-</div>
<div class= " riga " style= " margin-top:10px " >
<button id= " elimina " class= " pericolo " disabled>Elimina calibrazione</button >
</div>
</div>
<section class= " scheda " id= " sezioneSalvate " >
<h2 >Calibrazioni salvate </h2 >
<div id= " stato " class= " stato " >-</div>
<div class= " scorri " ><table id= " calibrazioni " class= " elenco " ></table></div >
<div id= " msgElenco " class= " messaggio " ></div >
</section>
</div>
</div>
@@ -208,7 +249,8 @@ function fmt(v, cifre) {
return n.toLocaleString( " it-IT " , o);
}
// numero senza separatore delle migliaia, per le caselle di input
function fmtCasella(v) { return Number(v).toLocaleString( " it-IT " , { maximumFractionDigits: 6, useGrouping: false }); }
// #87: cifre = decimali massimi; i parametri camera ne usano 1 (la camera rilegge 11999,993164 per 12000)
function fmtCasella(v, cifre) { return Number(v).toLocaleString( " it-IT " , { maximumFractionDigits: cifre === undefined ? 6 : cifre, useGrouping: false }); }
// legge un numero scritto con virgola o con punto
function leggiNumero(testo) {
var n = Number(String(testo).split( " " ).join( " " ).replace( " , " , " . " ));
@@ -274,6 +316,7 @@ function cambioSorgente(salva) {
fermaAnteprima();
azzeraIndicatori();
dire( " msgAnteprima " , " " );
dire( " msgElenco " , " " ); // #91
aggiornaStato();
caricaParametri();
if (era) avviaAnteprima();
@@ -281,7 +324,7 @@ function cambioSorgente(salva) {
// ---- Anteprima (#87)
var A = { attiva: false, gen: 0, inVolo: false, url: null, nitMax: 0 };
var A = { attiva: false, gen: 0, inVolo: false, url: null, nitMax: 0, ris: null }; // #91: ris = { w, h} dell ' ultimo frame
function azzeraIndicatori() {
A.nitMax = 0;
@@ -289,6 +332,8 @@ function azzeraIndicatori() {
$( " saturati " ).className = " valore " ;
$( " barraNitidezza " ).style.width = " 0 " ;
$( " raddrizzato " ).textContent = " " ;
A.ris = null; // #91: risoluzione del frame non piu ' nota
segnaRisoluzioni();
}
function pulsantiAnteprima() {
@@ -321,7 +366,12 @@ function aggiornaIndicatori(h) {
}
if (isFinite(media)) $( " media " ).textContent = fmt(media, 1);
var w = h.get( " X-Larghezza " ), a = h.get( " X-Altezza " );
if (w && a) $( " dimensioni " ).textContent = fmt(w, 0) + " x " + fmt(a, 0);
if (w && a) {
$( " dimensioni " ).textContent = fmt(w, 0) + " x " + fmt(a, 0);
// #91: risoluzione nota del frame, per segnare le calibrazioni che non combaciano
var nw = parseInt(w, 10), na = parseInt(a, 10);
if (!A.ris || A.ris.w !== nw || A.ris.h !== na) { A.ris = { w: nw, h: na }; segnaRisoluzioni(); }
}
var r = h.get( " X-Raddrizzato " );
$( " raddrizzato " ).textContent = r === " si " ? " raddrizzata dalla calibrazione " : r === " no " ? " non raddrizzata " : " " ;
}
@@ -357,7 +407,7 @@ async function unaAnteprima(lato) {
}
}
// ciclo sequenziale: la richiesta dopo parte solo a risposta arrivata, almeno 3 00 ms dopo la precedente
// ciclo sequenziale: la richiesta dopo parte solo a risposta arrivata, almeno 1 00 ms dopo la precedente (#90: ~10 fps)
async function ciclo(g) {
while (A.inVolo) await pausa(50);
while (A.attiva && g === A.gen) {
@@ -365,7 +415,7 @@ async function ciclo(g) {
var esito = await unaAnteprima(1280);
if (g !== A.gen) return;
if (esito === " fatale " ) { fermaAnteprima(); return; }
await pausa(esito === " errore " ? 1500 : Math.max(3 00 - (Date.now() - t0), 0));
await pausa(esito === " errore " ? 1500 : Math.max(1 00 - (Date.now() - t0), 0));
}
}
@@ -504,20 +554,20 @@ function creaParametro(nome, d) {
cur.disabled = bloccato;
cur.oninput = function () { // il cursore aggiorna la casella subito, la scrittura arriva dopo 300 ms
var v = adatta(d, daPosizione(d, Number(cur.value)));
casella.value = fmtCasella(v);
casella.value = fmtCasella(v, 1 );
modifica(nome, v);
};
campo.appendChild(cur);
}
casella.type = " text " ;
casella.inputMode = " decimal " ;
casella.value = fmtCasella(d.valore);
casella.value = fmtCasella(d.valore, 1 );
casella.disabled = bloccato;
casella.onchange = function () {
var v = leggiNumero(casella.value);
if (isNaN(v)) { dire( " msgParametri " , " Valore non valido per " + ETICHETTE[nome].toLowerCase() + " . " , " errore " ); casella.value = fmtCasella(P.dati[nome].valore); return; }
if (isNaN(v)) { dire( " msgParametri " , " Valore non valido per " + ETICHETTE[nome].toLowerCase() + " . " , " errore " ); casella.value = fmtCasella(P.dati[nome].valore, 1 ); return; }
v = adatta(d, v);
casella.value = fmtCasella(v);
casella.value = fmtCasella(v, 1 );
if (cur) cur.value = aPosizione(d, v);
modifica(nome, v);
};
@@ -588,7 +638,7 @@ function aggiornaValori(j) {
if (!el || P.pendenti.hasOwnProperty(nome)) return;
P.dati[nome].valore = d.valore;
if (el.select) el.select.value = d.valore;
if (el.casella && document.activeElement !== el.casella) el.casella.value = fmtCasella(d.valore);
if (el.casella && document.activeElement !== el.casella) el.casella.value = fmtCasella(d.valore, 1 );
if (el.cursore && document.activeElement !== el.cursore) el.cursore.value = aPosizione(P.dati[nome], d.valore);
});
}
@@ -610,6 +660,29 @@ $("salvaParametri").onclick = async function () {
}
};
// ---- Tab (#91): «Acquisizione» | «Calibrazione»; l ' anteprima sta fuori dai pannelli e resta sempre visibile
function scegliTab(nome, salva) {
var cal = nome === " cal " ;
$( " tabAcq " ).setAttribute( " aria-selected " , cal ? " false " : " true " );
$( " tabCal " ).setAttribute( " aria-selected " , cal ? " true " : " false " );
$( " tabAcq " ).tabIndex = cal ? -1 : 0;
$( " tabCal " ).tabIndex = cal ? 0 : -1;
$( " pannelloAcq " ).hidden = cal;
$( " pannelloCal " ).hidden = !cal;
if (salva) { memoria( " tmf.acq.tab " , nome); if (cal) caricaCalibrazioni(); } // l ' elenco si rilegge entrando
}
$( " tabAcq " ).onclick = function () { scegliTab( " acq " , true); };
$( " tabCal " ).onclick = function () { scegliTab( " cal " , true); };
[$( " tabAcq " ), $( " tabCal " )].forEach(function (b) { // frecce sinistra/destra fra le tab
b.onkeydown = function (e) {
if (e.key !== " ArrowLeft " && e.key !== " ArrowRight " ) return;
var cal = b.id === " tabAcq " ;
scegliTab(cal ? " cal " : " acq " , true);
$(cal ? " tabCal " : " tabAcq " ).focus();
};
});
// ---- Calibrazione (logica di #83 e #84; #87: lato precompilato, data italiana, conferma in pagina)
function righe(d) {
@@ -643,37 +716,6 @@ function tabella(el, d) {
});
}
async function aggiornaStato() {
var sorgente = $( " sorgente " ).value;
var stato = $( " stato " );
annullaConferma();
$( " elimina " ).disabled = true;
if (!sorgente) { stato.textContent = " - " ; $( " statoSorgente " ).textContent = " - " ; return; }
try {
var r = await fetch( " /camera/calibration?source= " + encodeURIComponent(sorgente));
if (sorgente !== $( " sorgente " ).value) return; // nel frattempo si e ' cambiata sorgente
if (r.status === 404) {
stato.textContent = " Sorgente non calibrata. " ;
$( " statoSorgente " ).textContent = " non calibrata " ;
return;
}
if (!r.ok) { stato.textContent = await dettaglio(r); return; }
var d = await r.json();
stato.textContent = " " ;
var t = document.createElement( " table " );
tabella(t, d);
var quando = document.createElement( " div " );
quando.textContent = " Calibrata il " + dataItaliana(d.calibrated_at);
stato.appendChild(t);
stato.appendChild(quando);
$( " elimina " ).disabled = false;
$( " statoSorgente " ).textContent = " calibrata: " + fmt(d.px_per_mm, 2) + " px/mm " ;
if (d.square_mm) $( " quadrato " ).value = fmtCasella(d.square_mm); // #87: parte dal lato gia ' usato
} catch (e) {
stato.textContent = " Servizio non raggiungibile. " ;
}
}
// avvisi sulla qualita ' (solo calibrazione densa): copertura bassa o residuo alto
function avvisi(d) {
var a = [];
@@ -686,78 +728,292 @@ function avvisi(d) {
return a.join( " " );
}
async function calcola(conFile, salva) {
// ---- Calibrazioni con nome (#91): archivio unico, una attiva per sorgente
var C = { elenco: [], attiva: null, celle: [], gen: 0, confermaNome: null, confermaBtn: null, confermaTimer: null };
var S = { nome: null, timer: null }; // secondo clic su «Sovrascrivi» entro 4 s
var JSON_H = { " Content-Type " : " application/json " };
function descriviOrigine(o) { return String(o || " - " ).replace( " : " , " " ); }
// stato della calibrazione attiva sulla sorgente selezionata (come prima di #91, piu ' il nome)
async function aggiornaStato() {
var sorgente = $( " sorgente " ).value;
if (!sorgente) { messaggio( " Scegliere una sorgente. " , " errore " ); return; }
var stato = $( " stato " );
annullaConferma();
$( " calSorgente " ).textContent = sorgente ? $( " sorgente " ).options[$( " sorgente " ).selectedIndex].textContent : " - " ; // #91
if (!sorgente) { stato.textContent = " - " ; $( " statoSorgente " ).textContent = " - " ; caricaCalibrazioni(); return; }
try {
var r = await fetch( " /camera/calibration?source= " + encodeURIComponent(sorgente));
if (sorgente !== $( " sorgente " ).value) return; // nel frattempo si e ' cambiata sorgente
if (r.status === 404) {
stato.textContent = " Sorgente non calibrata. " ;
$( " statoSorgente " ).textContent = " non calibrata " ;
} else if (!r.ok) {
stato.textContent = await dettaglio(r);
} else {
var d = await r.json();
stato.textContent = " " ;
var titolo = document.createElement( " div " );
titolo.textContent = " Calibrazione attiva: " + (d.nome ? " « " + d.nome + " » " : " (senza nome) " );
titolo.style.fontWeight = " 600 " ;
var t = document.createElement( " table " );
tabella(t, d);
var quando = document.createElement( " div " );
quando.textContent = " Calibrata il " + dataItaliana(d.calibrated_at);
stato.appendChild(titolo);
stato.appendChild(t);
stato.appendChild(quando);
$( " statoSorgente " ).textContent = " calibrata: " + fmt(d.px_per_mm, 2) + " px/mm " ;
if (d.square_mm) $( " quadrato " ).value = fmtCasella(d.square_mm); // #87: parte dal lato gia ' usato
}
} catch (e) {
stato.textContent = " Servizio non raggiungibile. " ;
}
caricaCalibrazioni();
}
// #91: elenco unico delle calibrazioni salvate; " attiva " e ' quella della sorgente selezionata
async function caricaCalibrazioni() {
var sorgente = $( " sorgente " ).value;
var g = ++C.gen;
try {
var r = await fetch( " /camera/calibrazioni " + (sorgente ? " ?source= " + encodeURIComponent(sorgente) : " " ));
if (g !== C.gen) return;
if (!r.ok) { dire( " msgElenco " , await dettaglio(r), " errore " ); return; }
var j = await r.json();
if (g !== C.gen) return;
C.elenco = j.calibrazioni || [];
C.attiva = j.attiva || null;
disegnaElenco();
} catch (e) {
if (g === C.gen) dire( " msgElenco " , " Servizio non raggiungibile. " , " errore " );
}
}
function cella(tr, testo) {
var td = document.createElement( " td " );
td.textContent = testo;
tr.appendChild(td);
return td;
}
function bottone(testo, classe, fn) {
var b = document.createElement( " button " );
b.textContent = testo;
b.className = classe || " " ;
b.onclick = fn;
return b;
}
function disegnaElenco() {
var t = $( " calibrazioni " );
annullaConferma();
t.textContent = " " ;
C.celle = [];
if (!C.elenco.length) {
var trVuota = document.createElement( " tr " );
cella(trVuota, " Nessuna calibrazione salvata. " );
t.appendChild(trVuota);
return;
}
var testa = document.createElement( " tr " );
[ " Nome " , " Risoluzione " , " px/mm " , " Metodo " , " Data " , " Origine " , " " ].forEach(function (h) {
var th = document.createElement( " th " );
th.textContent = h;
testa.appendChild(th);
});
t.appendChild(testa);
C.elenco.forEach(function (c) {
var tr = document.createElement( " tr " );
var tdNome = cella(tr, c.nome);
var attiva = C.attiva !== null && c.nome === C.attiva;
if (attiva) {
var s1 = document.createElement( " span " );
s1.className = " segno " ;
s1.textContent = " attiva " ;
tdNome.appendChild(s1);
}
var s2 = document.createElement( " span " ); // «risoluzione diversa» dal frame della sorgente (si accende in segnaRisoluzioni)
s2.className = " segno diversa " ;
s2.textContent = " risoluzione diversa " ;
s2.hidden = true;
tdNome.appendChild(s2);
C.celle.push( { w: Number(c.image_width), h: Number(c.image_height), span: s2 });
cella(tr, c.image_width && c.image_height ? fmt(c.image_width, 0) + " × " + fmt(c.image_height, 0) : " - " );
cella(tr, fmt(c.px_per_mm, 3));
cella(tr, c.metodo === " densa " ? " densa " : " planare " );
cella(tr, c.calibrated_at ? dataItaliana(c.calibrated_at) : " - " );
cella(tr, descriviOrigine(c.origine));
var az = document.createElement( " td " );
az.className = " azioni " ;
var bc = bottone( " Carica " , " " , function () { caricaNome(c.nome); });
bc.disabled = !$( " sorgente " ).value;
az.appendChild(bc);
if (attiva) az.appendChild(bottone( " Scollega " , " secondario " , scollega));
az.appendChild(bottone( " Elimina " , " secondario " , function (e) { eliminaNome(c.nome, e.currentTarget); }));
tr.appendChild(az);
t.appendChild(tr);
});
segnaRisoluzioni();
}
// #91: «risoluzione diversa» se il frame noto della sorgente non combacia con quello della calibrazione
function segnaRisoluzioni() {
(C.celle || []).forEach(function (c) {
c.span.hidden = !(A.ris && c.w > 0 && c.h > 0 && (A.ris.w !== c.w || A.ris.h !== c.h));
});
}
// «Carica»: la calibrazione diventa quella attiva della sorgente. Un errore (es. risoluzione diversa) non ferma l ' anteprima.
async function caricaNome(nome) {
var sorgente = $( " sorgente " ).value;
if (!sorgente) { dire( " msgElenco " , " Scegliere una sorgente nella tab Acquisizione. " , " errore " ); return; }
dire( " msgElenco " , " Caricamento di « " + nome + " »... " , " " );
try {
var r = await fetch( " /camera/calibrazione-attiva " , { method: " PUT " , headers: JSON_H, body: JSON.stringify( { source: sorgente, nome: nome }) });
if (sorgente !== $( " sorgente " ).value) return;
if (!r.ok) { dire( " msgElenco " , await dettaglio(r), " errore " ); return; }
dire( " msgElenco " , " Calibrazione « " + nome + " » caricata sulla sorgente. " , " ok " );
$( " raddrizzato " ).textContent = $( " grezzo " ).checked ? " non raddrizzata " : " raddrizzata dalla calibrazione " ; // il prossimo frame conferma
aggiornaStato();
} catch (e) {
dire( " msgElenco " , " Servizio non raggiungibile. " , " errore " );
}
}
// «Scollega»: la sorgente torna senza calibrazione, la libreria resta
async function scollega() {
var sorgente = $( " sorgente " ).value;
if (!sorgente) return;
try {
var r = await fetch( " /camera/calibration?source= " + encodeURIComponent(sorgente), { method: " DELETE " });
dire( " msgElenco " , r.ok ? " Calibrazione scollegata dalla sorgente (resta fra le salvate). " : await dettaglio(r), r.ok ? " ok " : " errore " );
if (r.ok) $( " raddrizzato " ).textContent = " non raddrizzata " ;
} catch (e) {
dire( " msgElenco " , " Servizio non raggiungibile. " , " errore " );
}
aggiornaStato();
}
// «Elimina» di una riga: conferma in pagina con un secondo clic entro 4 s (niente finestre del browser)
function annullaConferma() {
clearTimeout(C.confermaTimer);
C.confermaTimer = null;
if (C.confermaBtn) C.confermaBtn.textContent = " Elimina " ;
C.confermaBtn = null;
C.confermaNome = null;
}
async function eliminaNome(nome, btn) {
if (C.confermaNome !== nome) {
annullaConferma();
C.confermaNome = nome;
C.confermaBtn = btn;
btn.textContent = " Confermi? Clic ancora " ;
C.confermaTimer = setTimeout(annullaConferma, 4000);
return;
}
annullaConferma();
try {
var r = await fetch( " /camera/calibrazioni?nome= " + encodeURIComponent(nome), { method: " DELETE " });
dire( " msgElenco " , r.ok ? " Calibrazione « " + nome + " » eliminata. " : await dettaglio(r), r.ok ? " ok " : " errore " );
} catch (e) {
dire( " msgElenco " , " Servizio non raggiungibile. " , " errore " );
}
aggiornaStato();
}
// #91: il calcolo non salva mai; il servizio tiene il risultato e ne da ' il calcolo_id per «Salva»
async function calcola() {
var daFile = $( " origFile " ).checked;
var sorgente = $( " sorgente " ).value;
if (!daFile && !sorgente) { messaggio( " Scegliere una sorgente nella tab Acquisizione. " , " errore " ); return; }
var lato = leggiNumero($( " quadrato " ).value);
if (!(lato > 0)) { messaggio( " Il lato dello scacco deve essere un numero maggiore di zero. " , " errore " ); return; }
var f = new FormData();
f.append( " source " , sorgente);
f.append( " square_mm " , String(lato)); // al servizio sempre con il punto
f.append( " save " , salva ? " true " : " false " );
if (conFile) {
if (daFile) {
var file = $( " file " ).files[0];
if (!file) { messaggio( " Scegliere un file immagine. " , " errore " ); return; }
f.append( " image " , file);
} else {
f.append( " source " , sorgente);
}
var eraAttiva = A.attiva; // #87: l ' anteprima si sospende e poi riprende
var eraAttiva = !daFile && A.attiva; // #87: da sorgente l ' anteprima si sospende e poi riprende
if (eraAttiva) fermaAnteprima();
messaggio( " Calcolo in corso... " , " " );
[ " acquisisci " , " carica " , " salva " ].forEach(function (i) { $(i).disabled = true; });
[ " acquisisci " , " salva " ].forEach(function (i) { $(i).disabled = true; });
try {
var r = await fetch( " /camera/calibrate " , { method: " POST " , body: f });
if (!r.ok) { messaggio(await dettaglio(r), " errore " ); return; }
var d = await r.json();
ultimo = { conFile: conFile, dati: d };
ultimo = { dati: d };
tabella($( " tabella " ), d);
$( " overlay " ).src = " data:image/png;base64, " + d.overlay_png;
$( " avviso " ).textContent = avvisi(d);
$( " origineCalcolo " ).textContent = " Origine: " + descriviOrigine(d.origine);
$( " risultato " ).hidden = false;
messaggio(d.saved ? " Calibrazione salvata. " : " Calibrazione riuscita: controllare e salvare. " , " ok " );
if (d.saved) aggiornaStato( );
annullaSovrascrivi( );
dire( " msgSalva " , " " );
messaggio( " Calcolo riuscito: dare un nome e salvare. " , " ok " );
} catch (e) {
messaggio( " Servizio non raggiungibile. " , " errore " );
} finally {
[ " acquisisci " , " carica " , " salva " ].forEach(function (i) { $(i ).disabled = false; });
$( " acquisisci " ).disabled = false;
$( " salva " ).disabled = false;
if (eraAttiva) avviaAnteprima();
}
}
$( " acquisisci " ).onclick = function () { calcola(false, false); };
$( " carica " ).onclick = function () { calcola(true, false); };
// «Salva» rifa il calcolo con save=true sulla stessa origine (altra acquisizione se da ca mera )
$( " salva " ).onclick = function () { calcola(ultimo ? ultimo.conFile : false, true); } ;
// «Salva» con nome; se il nome c ' e ' gia ' il pulsante chiede un secondo clic per sovrascrivere
function annullaSovrascrivi() {
clearTimeout(S.ti mer);
S.timer = null ;
S.nome = null;
$( " salva " ).textContent = " Salva " ;
}
async function salvaNome() {
if (!ultimo || !ultimo.dati.calcolo_id) { dire( " msgSalva " , " Nessun calcolo da salvare: calcolare prima. " , " errore " ); return; }
var nome = $( " nomeCal " ).value.trim();
if (!nome) { dire( " msgSalva " , " Scrivere un nome per la calibrazione. " , " errore " ); return; }
if (nome.length > 60) { dire( " msgSalva " , " Il nome può avere al massimo 60 caratteri. " , " errore " ); return; }
var sovrascrivi = S.timer !== null && S.nome === nome.toLowerCase(); // secondo clic sullo stesso nome
annullaSovrascrivi();
$( " salva " ).disabled = true;
try {
var r = await fetch( " /camera/calibrazioni " , {
method: " POST " , headers: JSON_H,
body: JSON.stringify( { calcolo_id: ultimo.dati.calcolo_id, nome: nome, sovrascrivi: sovrascrivi })
});
if (r.status === 409) {
S.nome = nome.toLowerCase();
$( " salva " ).textContent = " Sovrascrivi « " + nome + " »? Clic ancora " ;
S.timer = setTimeout(annullaSovrascrivi, 4000);
dire( " msgSalva " , await dettaglio(r), " errore " );
return;
}
if (!r.ok) { dire( " msgSalva " , await dettaglio(r), " errore " ); return; }
dire( " msgSalva " , " Calibrazione « " + nome + " » salvata. Per usarla su una sorgente: «Carica» nell ' elenco. " , " ok " );
caricaCalibrazioni();
} catch (e) {
dire( " msgSalva " , " Servizio non raggiungibile. " , " errore " );
} finally {
$( " salva " ).disabled = false;
}
}
$( " acquisisci " ).onclick = calcola;
$( " salva " ).onclick = salvaNome;
$( " nomeCal " ).onkeydown = function (e) { if (e.key === " Enter " ) salvaNome(); };
// #87: il pulsante del file e ' in italiano, l ' input nativo resta nascosto
$( " scegliFile " ).onclick = function () { $( " file " ).click(); };
$( " file " ).onchange = function () {
$( " nomeFile " ).textContent = $( " file " ).files[0] ? $( " file " ).files[0].name : " nessuna immagine scelta " ;
};
// #87: «Elimina calibrazione» chiede conferma in pagina con un secondo clic (niente finestre del browser)
var confermaTimer = null;
function annullaConferma() {
clearTimeout(confermaTimer);
confermaTimer = null;
$( " elimina " ).textContent = " Elimina calibrazione " ;
}
$( " elimina " ).onclick = async function () {
if (!confermaTimer) {
$( " elimina " ).textContent = " Confermi? Clic ancora per eliminare " ;
confermaTimer = setTimeout(annullaConferma, 4000);
return;
}
annullaConferma();
var sorgente = $( " sorgente " ).value;
try {
var r = await fetch( " /camera/calibration?source= " + encodeURIComponent(sorgente), { method: " DELETE " });
messaggio(r.ok ? " Calibrazione eliminata. " : await dettaglio(r), r.ok ? " ok " : " errore " );
} catch (e) {
messaggio( " Servizio non raggiungibile. " , " errore " );
}
aggiornaStato();
if ($( " file " ).files[0]) $( " origFile " ).checked = true; // #91: scelto un file, l ' origine diventa il file
};
$( " sorgente " ).onchange = function () { cambioSorgente(true); };
scegliTab(memoria( " tmf.acq.tab " ) === " cal " ? " cal " : " acq " , false); // #91: tab ricordata
caricaSorgenti();
</script>
</body>