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 4c07d70..00e2c38 100644 --- a/src/frontend/flask_app/static/js/dxf_recipe_wizard.js +++ b/src/frontend/flask_app/static/js/dxf_recipe_wizard.js @@ -41,6 +41,9 @@ function dxfRecipeWizard(taskId, csrf, msgs) { imageSource: '', // #104: '' | 'file' | 'camera'; il campo mm/px vale solo per 'file' tested: false, // #104: true dopo una Prova (anche fallita): mostra la sezione del risultato zoom: 1, // #104: scala dell'immagine di prova (1 = pixel nativi) + fitZoom: 1, // #104: zoom di Adatta (sotto o pari a questo il viewport segue l'immagine) + panning: false, // #104: trascinamento in corso + _pan: null, // #104: {x, y, left, top} al pointerdown resPx: null, // #104: {w, h} in pixel dell'immagine di prova (per la dimensione del wrapper) runPxPerMm: null, // #104: scala usata nell'ultima Prova (null = calibrazione stazione) runBandMm: null, // #104: banda usata nell'ultima Prova @@ -412,6 +415,32 @@ function dxfRecipeWizard(taskId, csrf, msgs) { get zoomPct() { return Math.round(this.zoom * 100) + ' %'; }, get zoomW() { return this.resPx ? this.resPx.w * this.zoom : 0; }, get zoomH() { return this.resPx ? this.resPx.h * this.zoom : 0; }, + // #104: altezza massima del viewport = min(70vh, 520px) + _maxVpH() { return Math.min(0.7 * (typeof window !== 'undefined' && window.innerHeight ? window.innerHeight : 800), 520); }, + // #104: con zoom <= Adatta il viewport e' alto come l'immagine scalata (min 200px), altrimenti al massimo + get vpHeight() { + const max = this._maxVpH(); + if (!this.resPx || this.zoom > this.fitZoom * 1.0001) return max; + return Math.min(max, Math.max(200, this.zoomH)); + }, + // #104: l'immagine e' piu' grande dello spazio: si puo' trascinare + get canPan() { return !!this.resPx && this.zoom > this.fitZoom * 1.0001; }, + get panCursor() { return this.canPan ? (this.panning ? 'grabbing' : 'grab') : 'default'; }, + // #104: pan col puntatore (mouse e touch): scroll del viewport di quanto si muove il puntatore + onPanDown(ev) { + const vp = this.$refs.viewport; + if (!vp || (ev.pointerType === 'mouse' && ev.button)) return; + this._pan = { x: ev.clientX, y: ev.clientY, left: vp.scrollLeft, top: vp.scrollTop }; + this.panning = true; + try { vp.setPointerCapture(ev.pointerId); } catch (e) { /* puntatore gia' rilasciato */ } + }, + onPanMove(ev) { + const vp = this.$refs.viewport; + if (!this._pan || !vp) return; + vp.scrollLeft = this._pan.left - (ev.clientX - this._pan.x); + vp.scrollTop = this._pan.top - (ev.clientY - this._pan.y); + }, + onPanEnd() { this._pan = null; this.panning = false; }, zoomBy(f) { const vp = this.$refs.viewport; const old = this.zoom; @@ -423,11 +452,13 @@ function dxfRecipeWizard(taskId, csrf, msgs) { this.zoom = z; if (vp) this.$nextTick(() => { vp.scrollLeft = cx * z - vp.clientWidth / 2; vp.scrollTop = cy * z - vp.clientHeight / 2; }); }, - // #104: Fit = tutta l'immagine nel viewport + // #104: Fit = tutta l'immagine nel viewport (altezza massima, non quella corrente che segue l'immagine) fit() { const vp = this.$refs.viewport, img = this.$refs.img; - if (!vp || !img || !img.naturalWidth || !vp.clientWidth || !vp.clientHeight) return; - this.zoom = Math.min(8, Math.max(0.05, Math.min(vp.clientWidth / img.naturalWidth, vp.clientHeight / img.naturalHeight))); + if (!vp || !img || !img.naturalWidth || !vp.clientWidth) return; + const z = Math.min(8, Math.max(0.05, Math.min(vp.clientWidth / img.naturalWidth, this._maxVpH() / img.naturalHeight))); + this.zoom = z; + this.fitZoom = z; vp.scrollLeft = 0; vp.scrollTop = 0; }, // #104: Ctrl+rotella = zoom diff --git a/src/frontend/flask_app/templates/maker/task_editor.html b/src/frontend/flask_app/templates/maker/task_editor.html index 550a89c..afe7beb 100644 --- a/src/frontend/flask_app/templates/maker/task_editor.html +++ b/src/frontend/flask_app/templates/maker/task_editor.html @@ -776,20 +776,27 @@