From 20f33234a8367c630b03cf4c2fd8aa2deedc1ace Mon Sep 17 00:00:00 2001 From: "Adriano (daitarn)" Date: Sun, 25 Oct 2026 16:14:10 +0100 Subject: [PATCH] maker: anteprima immagine nel wizard Crea ricetta da DXF (Refs #102) Anteprima dell'immagine scelta/acquisita accanto a quella del DXF, con pixel e campo in mm (scala a mano o calibrazione stazione); fallback se il formato non e' mostrabile. Test JS e di pagina, voci it/en. Co-Authored-By: Claude Sonnet 5.5 --- .../flask_app/static/js/dxf_recipe_wizard.js | 43 ++++++++++++++++--- .../templates/maker/task_editor.html | 11 ++++- .../flask_app/tests/js/test_dxf_preview.js | 19 ++++++++ .../flask_app/tests/test_dxf_recipe_routes.py | 1 + .../translations/en/LC_MESSAGES/messages.po | 8 ++++ .../translations/it/LC_MESSAGES/messages.po | 8 ++++ 6 files changed, 84 insertions(+), 6 deletions(-) diff --git a/src/frontend/flask_app/static/js/dxf_recipe_wizard.js b/src/frontend/flask_app/static/js/dxf_recipe_wizard.js index 6d7048e..b2d2777 100644 --- a/src/frontend/flask_app/static/js/dxf_recipe_wizard.js +++ b/src/frontend/flask_app/static/js/dxf_recipe_wizard.js @@ -20,6 +20,9 @@ function dxfRecipeWizard(taskId, csrf, msgs) { imageFile: null, // immagine caricata o acquisita imageName: '', imageSrc: '', + imagePx: null, // #102: {w, h} in pixel, letto dall' dell'anteprima + imageBroken: false, // #102: il browser non sa mostrare il formato (il file resta valido per l'invio) + calPxMm: null, // #102: px/mm della calibrazione della stazione dxfFile: null, dxfPreview: null, // #101: risposta di /dxf-preview {contorni, bbox, larghezza_mm, altezza_mm, entita, punti} dxfPreviewBusy: false, @@ -54,13 +57,17 @@ function dxfRecipeWizard(taskId, csrf, msgs) { // Cambio stazione: rilegge lo stato di calibrazione dall'acquisitore async onStation() { - this.calState = ''; this.calLabel = ''; this.calUrl = ''; + this.calState = ''; this.calLabel = ''; this.calUrl = ''; this.calPxMm = null; this.result = null; if (!this.stationId) return; const r = await fetchStationCalibration(this.stationId); this.calState = r.state || 'unreachable'; this.calUrl = r.calibration_url || ''; - if (this.calState === 'calibrated') this.calLabel = formatCalibration(r.calibration || {}); + if (this.calState === 'calibrated') { + this.calLabel = formatCalibration(r.calibration || {}); + const c = Number((r.calibration || {}).px_per_mm); // #102: scala per il campo in mm dell'anteprima + this.calPxMm = Number.isFinite(c) && c > 0 ? c : null; + } }, get calibrated() { return this.calState === 'calibrated'; }, @@ -81,12 +88,38 @@ function dxfRecipeWizard(taskId, csrf, msgs) { _setImage(file) { if (this.imageSrc) URL.revokeObjectURL(this.imageSrc); - this.imageFile = file; - this.imageName = file.name || ''; - this.imageSrc = URL.createObjectURL(file); + // #102: con file null (immagine tolta) si azzera anche l'anteprima + this.imageFile = file || null; + this.imageName = (file && file.name) || ''; + this.imageSrc = file ? URL.createObjectURL(file) : ''; + this.imagePx = null; + this.imageBroken = false; this.result = null; }, + // #102: l' dell'anteprima e' caricata: legge i pixel; se non si carica il formato non e' mostrabile + onImagePreviewLoad(el) { + this.imageBroken = false; + this.imagePx = el && el.naturalWidth ? { w: el.naturalWidth, h: el.naturalHeight } : null; + }, + onImagePreviewError() { + this.imageBroken = !!this.imageSrc; + this.imagePx = null; + }, + // #102: nome usato dal template (stesso valore dell'URL gia' usato per l'overlay dei risultati) + get imagePreviewUrl() { return this.imageSrc; }, + // #102: «W × H px · campo ≈ L × H mm»; scala = mm/px a mano se valido, altrimenti calibrazione della stazione + get imagePreviewSummary() { + const p = this.imagePx; + if (!p) return ''; + const lang = document.documentElement.lang || 'it'; + const f = (v, d) => Number(v).toLocaleString(lang, { minimumFractionDigits: d, maximumFractionDigits: d }); + let text = `${f(p.w, 0)} × ${f(p.h, 0)} px`; + const pxMm = this.manualPxMm !== null ? this.manualPxMm : (this.calibrated ? this.calPxMm : null); + if (pxMm) text += ` · ${this.msgs.imageField || 'field'} ≈ ${f(p.w / pxMm, 2)} × ${f(p.h / pxMm, 2)} mm`; + return text; + }, + onImage(ev) { const f = ev.target.files && ev.target.files[0]; if (f) this._setImage(f); diff --git a/src/frontend/flask_app/templates/maker/task_editor.html b/src/frontend/flask_app/templates/maker/task_editor.html index 7101356..849e910 100644 --- a/src/frontend/flask_app/templates/maker/task_editor.html +++ b/src/frontend/flask_app/templates/maker/task_editor.html @@ -662,7 +662,7 @@