acquisitore: «Attiva calibrazione» con scelta e CONFERMA sotto l'immagine calibrata (Refs #93)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-24 17:03:16 +02:00
parent 9c45a51770
commit 800ec41254
3 changed files with 82 additions and 16 deletions
+62 -14
View File
@@ -46,6 +46,8 @@ PAGINA = r"""<!doctype html>
select:focus, input:focus, button:focus-visible { outline: 2px solid var(--accento); outline-offset: 1px; } select:focus, input:focus, button:focus-visible { outline: 2px solid var(--accento); outline-offset: 1px; }
input[type=range] { width: 100%; accent-color: var(--accento); } input[type=range] { width: 100%; accent-color: var(--accento); }
input[type=checkbox] { accent-color: var(--accento); } input[type=checkbox] { accent-color: var(--accento); }
.scelta-cal { margin-top: 8px; align-items: center; } /* #93 */
.scelta-cal select { flex: 1 1 200px; min-width: 0; } /* #93: nessuno scorrimento a 400 px */
.riga { display: flex; flex-wrap: wrap; gap: 10px; align-items: flex-end; } .riga { display: flex; flex-wrap: wrap; gap: 10px; align-items: flex-end; }
.nota { font-size: .82rem; color: var(--secondario); margin: 8px 0 0; } .nota { font-size: .82rem; color: var(--secondario); margin: 8px 0 0; }
button { padding: 7px 13px; font-size: .95rem; border: 0; border-radius: 6px; background: var(--accento); color: #fff; button { padding: 7px 13px; font-size: .95rem; border: 0; border-radius: 6px; background: var(--accento); color: #fff;
@@ -156,7 +158,7 @@ PAGINA = r"""<!doctype html>
<button id="avvia">Avvia anteprima</button> <button id="avvia">Avvia anteprima</button>
<button id="ferma" class="secondario" disabled>Ferma anteprima</button> <button id="ferma" class="secondario" disabled>Ferma anteprima</button>
<button id="scatta" class="secondario">Scatta</button> <button id="scatta" class="secondario">Scatta</button>
<label style="margin:0 0 6px"><input id="mostraCal" type="checkbox"> Mostra immagine calibrata</label> <label style="margin:0 0 6px"><input id="mostraCal" type="checkbox"> Attiva calibrazione</label>
<label style="margin:0 0 6px"><input id="mirinoCasella" type="checkbox"> Mirino centrale</label> <label style="margin:0 0 6px"><input id="mirinoCasella" type="checkbox"> Mirino centrale</label>
</div> </div>
<div class="riquadri" id="riquadri"> <div class="riquadri" id="riquadri">
@@ -179,6 +181,12 @@ PAGINA = r"""<!doctype html>
<div id="mirinoCal" class="mirino" hidden></div> <div id="mirinoCal" class="mirino" hidden></div>
</div> </div>
</div> </div>
<!-- #93: scelta della calibrazione da attivare sulla sorgente, con CONFERMA -->
<div class="riga scelta-cal">
<select id="sceltaCal" aria-label="Calibrazione da attivare" disabled></select>
<button id="confermaCal" type="button" disabled>CONFERMA</button>
</div>
<div id="msgSceltaCal" class="messaggio"></div>
</div> </div>
</div> </div>
<div class="indicatori"> <div class="indicatori">
@@ -618,7 +626,7 @@ $("avvia").onclick = avviaAnteprima;
$("ferma").onclick = fermaAnteprima; $("ferma").onclick = fermaAnteprima;
$("scatta").onclick = scatta; $("scatta").onclick = scatta;
$("azzeraMax").onclick = function () { A.nitMax = 0; $("nitidezzaMax").textContent = "-"; $("barraNitidezza").style.width = "0"; }; $("azzeraMax").onclick = function () { A.nitMax = 0; $("nitidezzaMax").textContent = "-"; $("barraNitidezza").style.width = "0"; };
// #92: il mirino vale per entrambi i riquadri; la casella «Mostra immagine calibrata» apre il riquadro e raddrizza il frame gia' tenuto // #92: il mirino vale per entrambi i riquadri; #93: la casella «Attiva calibrazione» apre il riquadro e raddrizza il frame gia' tenuto
$("mirinoCasella").onchange = function () { $("mirino").hidden = $("mirinoCal").hidden = !$("mirinoCasella").checked; }; $("mirinoCasella").onchange = function () { $("mirino").hidden = $("mirinoCal").hidden = !$("mirinoCasella").checked; };
function mostraRiquadroCal() { function mostraRiquadroCal() {
var si = $("mostraCal").checked; var si = $("mostraCal").checked;
@@ -995,6 +1003,7 @@ function disegnaElenco() {
annullaConferma(); annullaConferma();
t.textContent = ""; t.textContent = "";
C.celle = []; C.celle = [];
disegnaSceltaCal(); // #93: la combo sotto la calibrata segue l'elenco, anche se vuoto
if (!C.elenco.length) { if (!C.elenco.length) {
var vuota = document.createElement("div"); var vuota = document.createElement("div");
vuota.className = "nota"; vuota.className = "nota";
@@ -1035,7 +1044,7 @@ function disegnaElenco() {
var bc = bottone("Carica", "", function () { caricaNome(c.nome); }); var bc = bottone("Carica", "", function () { caricaNome(c.nome); });
bc.disabled = !$("sorgente").value; bc.disabled = !$("sorgente").value;
az.appendChild(bc); az.appendChild(bc);
if (attiva) az.appendChild(bottone("Scollega", "secondario", scollega)); if (attiva) az.appendChild(bottone("Scollega", "secondario", function () { scollega("msgElenco"); })); // #93: senza l'evento come parametro
az.appendChild(bottone("Elimina", "secondario", function (e) { eliminaNome(c.nome, e.currentTarget); })); az.appendChild(bottone("Elimina", "secondario", function (e) { eliminaNome(c.nome, e.currentTarget); }));
sc.appendChild(riga1); sc.appendChild(riga1);
sc.appendChild(riga2); sc.appendChild(riga2);
@@ -1050,41 +1059,80 @@ function segnaRisoluzioni() {
(C.celle || []).forEach(function (c) { (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)); c.span.hidden = !(A.ris && c.w > 0 && c.h > 0 && (A.ris.w !== c.w || A.ris.h !== c.h));
}); });
// #93: stesso avviso nelle voci della combo; si cambia solo il testo, cosi' scelta e menu aperto restano
var sel = $("sceltaCal");
for (var i = 0; i < sel.options.length; i++) if (sel.options[i].value) sel.options[i].textContent = testoVoceCal(sel.options[i].dataset);
}
// #93: testo di una voce della combo: «nome · W×H · px/mm», con «risoluzione diversa» se il frame noto non combacia
function testoVoceCal(d) {
var w = Number(d.w), h = Number(d.h);
var t = d.nome + " · " + (w && h ? fmt(w, 0) + "×" + fmt(h, 0) : "-") + " · " + fmt(Number(d.px), 3) + " px/mm";
if (A.ris && w > 0 && h > 0 && (A.ris.w !== w || A.ris.h !== h)) t += " — risoluzione diversa";
return t;
}
// #93: ridisegna la combo (ogni ricarica dell'elenco); preselezionata la calibrazione attiva, senza sorgente tutto spento
function disegnaSceltaCal() {
var sel = $("sceltaCal");
sel.textContent = "";
var nessuna = document.createElement("option");
nessuna.value = "";
nessuna.textContent = "— nessuna (non calibrata) —";
sel.appendChild(nessuna);
(C.elenco || []).forEach(function (c) {
var o = document.createElement("option");
o.value = c.nome;
o.dataset.nome = c.nome; o.dataset.w = c.image_width || 0; o.dataset.h = c.image_height || 0; o.dataset.px = c.px_per_mm || 0;
o.textContent = testoVoceCal(o.dataset);
sel.appendChild(o);
});
sel.value = C.attiva !== null && C.attiva !== undefined ? C.attiva : "";
if (sel.selectedIndex < 0) sel.selectedIndex = 0;
sel.disabled = $("confermaCal").disabled = !$("sorgente").value;
} }
// «Carica»: la calibrazione diventa quella attiva della sorgente. Un errore (es. risoluzione diversa) non ferma l'anteprima. // «Carica»: la calibrazione diventa quella attiva della sorgente. Un errore (es. risoluzione diversa) non ferma l'anteprima.
async function caricaNome(nome) { async function caricaNome(nome, idMsg) {
idMsg = idMsg || "msgElenco"; // #93: il messaggio puo' stare sotto la calibrata (msgSceltaCal)
var sorgente = $("sorgente").value; var sorgente = $("sorgente").value;
if (!sorgente) { dire("msgElenco", "Scegliere una sorgente nella tab Acquisizione.", "errore"); return; } if (!sorgente) { dire(idMsg, "Scegliere una sorgente nella tab Acquisizione.", "errore"); return; }
dire("msgElenco", "Caricamento di «" + nome + "»...", ""); dire(idMsg, "Caricamento di «" + nome + "»...", "");
try { try {
var r = await fetch("/camera/calibrazione-attiva", { method: "PUT", headers: JSON_H, body: JSON.stringify({ source: sorgente, nome: nome }) }); 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 (sorgente !== $("sorgente").value) return;
if (!r.ok) { dire("msgElenco", await dettaglio(r), "errore"); return; } if (!r.ok) { dire(idMsg, await dettaglio(r), "errore"); disegnaSceltaCal(); return; } // #93: la combo torna sull'attiva
dire("msgElenco", "Calibrazione «" + nome + "» caricata sulla sorgente.", "ok"); dire(idMsg, "Calibrazione «" + nome + "» caricata sulla sorgente.", "ok");
A.bloccoCal = false; // #92 A.bloccoCal = false; // #92
await aggiornaStato(); // prima lo stato nuovo, poi il riquadro calibrata con la nuova calibrazione await aggiornaStato(); // prima lo stato nuovo (rilegge anche l'elenco, #93: schede e combo allineate), poi il riquadro calibrata
aggiornaCalibrata(); aggiornaCalibrata();
} catch (e) { } catch (e) {
dire("msgElenco", "Servizio non raggiungibile.", "errore"); dire(idMsg, "Servizio non raggiungibile.", "errore");
disegnaSceltaCal(); // #93
} }
} }
// «Scollega»: la sorgente torna senza calibrazione, la libreria resta // «Scollega»: la sorgente torna senza calibrazione, la libreria resta
async function scollega() { async function scollega(idMsg) {
idMsg = idMsg || "msgElenco"; // #93
var sorgente = $("sorgente").value; var sorgente = $("sorgente").value;
if (!sorgente) return; if (!sorgente) return;
try { try {
var r = await fetch("/camera/calibration?source=" + encodeURIComponent(sorgente), { method: "DELETE" }); 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"); dire(idMsg, r.ok ? "Calibrazione scollegata dalla sorgente (resta fra le salvate)." : await dettaglio(r), r.ok ? "ok" : "errore");
} catch (e) { } catch (e) {
dire("msgElenco", "Servizio non raggiungibile.", "errore"); dire(idMsg, "Servizio non raggiungibile.", "errore");
} }
A.bloccoCal = false; // #92 A.bloccoCal = false; // #92
await aggiornaStato(); await aggiornaStato(); // #93: rilegge anche l'elenco, quindi la combo torna sull'attiva anche dopo un errore
aggiornaCalibrata(); // #92: senza calibrazione il riquadro dice che non c'e' aggiornaCalibrata(); // #92: senza calibrazione il riquadro dice che non c'e'
} }
// #93: CONFERMA sotto la calibrata: voce scelta -> PUT calibrazione-attiva, «nessuna» -> DELETE calibration
$("confermaCal").onclick = function () {
var nome = $("sceltaCal").value;
if (nome) caricaNome(nome, "msgSceltaCal"); else scollega("msgSceltaCal");
};
// «Elimina» di una riga: conferma in pagina con un secondo clic entro 4 s (niente finestre del browser) // «Elimina» di una riga: conferma in pagina con un secondo clic entro 4 s (niente finestre del browser)
function annullaConferma() { function annullaConferma() {
clearTimeout(C.confermaTimer); clearTimeout(C.confermaTimer);
+1 -1
View File
@@ -42,7 +42,7 @@ def test_id_principali():
def test_sezioni_e_testi_italiani(): def test_sezioni_e_testi_italiani():
for testo in ("Sorgente", "Anteprima", "Parametri camera", "Calibrazione", for testo in ("Sorgente", "Anteprima", "Parametri camera", "Calibrazione",
"Salva come predefiniti", "Carica immagine da file...", "Questa sorgente non ha parametri regolabili", "Salva come predefiniti", "Carica immagine da file...", "Questa sorgente non ha parametri regolabili",
"Mostra immagine calibrata"): # #92 "Attiva calibrazione"): # #92 #93
assert testo in PAGINA, testo assert testo in PAGINA, testo
assert "it-IT" in PAGINA # numeri con la virgola assert "it-IT" in PAGINA # numeri con la virgola
assert "Choose File" not in PAGINA assert "Choose File" not in PAGINA
+19 -1
View File
@@ -30,7 +30,7 @@ def test_sorgente_in_cima_poi_riquadri():
assert i_acq < i_sorg < i_ant < i_riq < PAGINA.index('id="sezioneParametri"') < i_cal assert i_acq < i_sorg < i_ant < i_riq < PAGINA.index('id="sezioneParametri"') < i_cal
assert PAGINA.index('id="sezioneCalibrazione"') > i_cal assert PAGINA.index('id="sezioneCalibrazione"') > i_cal
# barra: casella calibrata e mirino; la vecchia casella «Immagine grezza» non c'e' piu' # barra: casella calibrata e mirino; la vecchia casella «Immagine grezza» non c'e' piu'
assert 'id="mostraCal"' in PAGINA and "Mostra immagine calibrata" in PAGINA assert 'id="mostraCal"' in PAGINA and "Attiva calibrazione" in PAGINA
assert 'id="grezzo"' not in PAGINA and "Immagine grezza (senza correzione)" not in PAGINA assert 'id="grezzo"' not in PAGINA and "Immagine grezza (senza correzione)" not in PAGINA
# riquadri: «Immagine» sempre visibile, «Calibrata» nascosta finche' la casella e' spenta # riquadri: «Immagine» sempre visibile, «Calibrata» nascosta finche' la casella e' spenta
assert re.search(r'<div class="riquadro" id="riquadroCal" hidden>', PAGINA) assert re.search(r'<div class="riquadro" id="riquadroCal" hidden>', PAGINA)
@@ -124,3 +124,21 @@ def test_elenco_sorgenti_senza_attese_e_calibrata_solo_se_serve():
assert "nessuna calibrazione attiva su questa sorgente" in PAGINA assert "nessuna calibrazione attiva su questa sorgente" in PAGINA
assert "if (r.status === 409) A.bloccoCal = true;" in PAGINA and "if (A.bloccoCal) return;" in PAGINA assert "if (r.status === 409) A.bloccoCal = true;" in PAGINA and "if (A.bloccoCal) return;" in PAGINA
assert PAGINA.count("A.bloccoCal = false;") >= 3 # cambio sorgente, Carica, Scollega assert PAGINA.count("A.bloccoCal = false;") >= 3 # cambio sorgente, Carica, Scollega
def test_attiva_calibrazione_con_scelta_e_conferma():
# #93: sotto la calibrata una combo e CONFERMA; la casella cambia nome
for i in ('id="sceltaCal"', 'id="confermaCal"', 'id="msgSceltaCal"'):
assert i in PAGINA, i
assert "Attiva calibrazione" in PAGINA and ">CONFERMA</button>" in PAGINA
assert "Mostra immagine calibrata" not in PAGINA
assert PAGINA.index('id="riquadroCal"') < PAGINA.index('id="sceltaCal"') < PAGINA.index('id="confermaCal"') < PAGINA.index('id="msgSceltaCal"')
# il gestore di CONFERMA usa PUT calibrazione-attiva (via caricaNome) o DELETE calibration (via scollega), messaggio sotto la calibrata
gestore = PAGINA[PAGINA.index('$("confermaCal").onclick'):]
gestore = gestore[:gestore.index("};")]
assert 'caricaNome(nome, "msgSceltaCal")' in gestore and 'scollega("msgSceltaCal")' in gestore
assert 'method: "PUT"' in PAGINA and "/camera/calibrazione-attiva" in PAGINA
assert 'method: "DELETE"' in PAGINA and '"/camera/calibration?source="' in PAGINA
# voce vuota «nessuna», suffisso di risoluzione, combo ridisegnata con l'elenco e spenta senza sorgente
assert "— nessuna (non calibrata) —" in PAGINA and "— risoluzione diversa" in PAGINA
assert "disegnaSceltaCal();" in PAGINA and "= !$(\"sorgente\").value" in PAGINA