Visualizzatore immagine standard (zoom, Adatta, Salva) usato da tutte le immagini #122
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Cosa: un solo componente con zoom −/+, Adatta, Salva, %, Ctrl+rotella, pan, overlay a tratto costante ed etichette, al posto delle quattro implementazioni (pannello visione #115, wizard DXF immagine/DXF #113 e Prova #104, pagina camera 8765 #111). Perché: Adriano 28/10 sera, «visualizzatore immagine deve essere elemento standard usato ovunque»; Salva anche su DXF del wizard e pagine camera. Fatto quando: tutte le immagini elencate nell'analisi usano lo stesso componente, nessuno zoom duplicato, test verdi, prova in Chrome.
ANALISI (Opus, passo 1) — visualizzatore immagine standard
RIF: gitea:Adriano/TieMeasureFlow#122. Base: ramo
lavoro/daitarn-tmf-dxfcaldopo le correzioni della revisione di #118-#121 (hash nel prompt degli agenti).COSA: un solo componente
<tmf-viewer>(Custom Element, JS puro, senza Alpine né Tailwind) con zoom −/+, Adatta, Salva, percentuale, Ctrl+rotella, pan col puntatore, overlay SVG a tratto costante ed etichette. Sostituisce le quattro implementazioni di oggi:vision_execute.jsstatoz);views.imgeviews.dxf), Prova (#104,zoom/fit);creaVisoreinscripts/calibrazione_pagina.py), più l'immagine degli angoli della scacchiera (<img id="overlay">, oggi senza zoom).Decisioni di Adriano (28/10 sera): «visualizzatore immagine deve essere elemento standard usato ovunque»; Salva anche sul DXF del wizard e sulle pagine camera; NON su: miniature degli elenchi ricette, disegno del calibro dell'operatore, pagine maker (recipe_preview, recipe_editor, anteprima disegno del task).
Perché un Custom Element: la pagina camera è HTML autonomo servito da
scripts/camera_service.py(niente Alpine, niente Flask), le pagine Flask usano Alpine; un elemento standard funziona in entrambe con lo stesso file.CONTRATTO
<tmf-viewer>File:
src/frontend/flask_app/static/js/image_viewer.js, script classico. DefiniscecustomElements.define('tmf-viewer', ...)una volta sola (guardia se già definito). Inietta una sola volta nel<head>un<style id="tmf-viewer-css">con regole scopedtmf-viewer ...: il componente ha lo stesso aspetto ovunque senza Tailwind. Light DOM (niente shadow DOM), così le classi delle etichette restano della pagina. Esporta per i test nodemodule.exports = { TmfViewerMath }, con funzioni pure:fitZoom(boxW, boxH, w, h)→ zoom che fa stare tutto il contenuto nel box (0 se non calcolabile);clampZoom(z, fit)→ limiti [fit·0.5, max(8, fit)];zoomAround(z0, z1, scrollL, scrollT, px, py)→ nuovo scroll che tiene fermo il punto (px,py) del viewport;pct(z)→ testo «NNN %».Il modulo si carica in node senza
window/document(accessi solo dentro funzioni e classe definita solo secustomElementsesiste).Struttura interna creata in
connectedCallback(una volta):tmf-viewer > .tmfv-bar(− + Adatta Salva %) e.tmfv-vp(viewport che scorre)> .tmfv-stage(larghezza w·zoom, altezza h·zoom, margin auto)> [img.tmfv-img | contenuto] + svg.tmfv-ov + div.tmfv-labels.Attributi:
src— URL dell'immagine (modo immagine). Al cambio ricarica; Adatta al primo load e quando cambiano le dimensioni naturali; mantiene lo zoom se le dimensioni restano uguali (frame successivi della stessa camera).width,height— dimensioni logiche del contenuto in px (opzionali). Default: dimensioni naturali dell'immagine. Servono alla pagina camera (anteprima ridotta alato_max, zoom e overlay riferiti ai pixel della camera) e al modo contenuto.save-name— nome base per Salva (defaultimmagine);save-src— URL da scaricare per Salva se diverso dasrc(frame pieno invece dell'anteprima);no-save— niente pulsante Salva.fit-height— opzionale: in Adatta l'altezza del componente segue l'immagine invece di riempire il box (comportamento #104 della Prova). Senza, il componente riempie il box che la pagina gli dà (display:block, la pagina imposta l'altezza con una classe o con style).Modo contenuto: se non c'è
srce il componente ha un figlio<svg>(es. DXF), quel figlio è spostato nello stage e ridimensionato al 100 %; dimensioni logiche dagli attributiwidth/heightoppure dalviewBoxdel figlio. Salva rasterizza il SVG in PNG (sfondo bianco, lato lungo al più 4096 px) conImage()su blobimage/svg+xml+ canvas (createImageBitmapnon decodifica SVG).Proprietà JS:
overlay— array di forme vs-tools: disegnato conVisionOverlay.svg(overlay, {zoom})sewindow.VisionOverlayc'è; ridisegnato a ogni cambio di zoom;viewBoxdell'svg = dimensioni logiche.labels— array{key, x, y, text, cls}in px logici: div assoluti in.tmfv-labelsaleft: x/w %,top: y/h %,transform: translate(8px,-50%), classecls(della pagina) + classe basetmfv-label. Testo contextContent, mai innerHTML.zoom(sola lettura), metodifit(),zoomBy(f).Eventi (bubbling):
viewer-load{width, height}dopo il load dell'immagine;viewer-zoom{zoom, fit}a ogni cambio.Comportamento: Ctrl+rotella = zoom intorno al puntatore (senza Ctrl la rotella scorre la pagina); pan con trascinamento (mouse sinistro, dito) solo quando il contenuto supera il viewport; cursore grab/grabbing; ResizeObserver: se l'utente non ha zoomato (stato «adatta»), al resize rifà Adatta; barra in alto a destra sopra il contenuto,
z-indexsopra overlay ed etichette; pulsantitype="button". Testi dei pulsanti: attributi opzionalilabel-fit,label-save,title-in,title-out,title-save(default italiani «Adatta», «Salva», «Ingrandisci», «Riduci», «Salva immagine»), così Jinja passa{{ _('...') }}.Salva:
tmfSaveImage(saveSrc || src, saveName)distatic/js/save_image.js(già esistente, apre «Salva con nome» prima di scaricare); nel modo contenuto, rasterizzazione come sopra e poi lo stesso salvataggio (aggiungere asave_image.jstmfSaveBlob(blob, baseName)riusato datmfSaveImage).Trappole: Alpine 3.15.12 — un
:stylestringa sul<tmf-viewer>cancella lo style statico; maix-showe:stylestringa sullo stesso elemento; le proprietà si impostano conx-effect="$el.overlay = ...; $el.labels = ...", gli attributi con:src.PIANI (file disgiunti, uno per agente)
visore:static/js/image_viewer.js(nuovo),static/js/save_image.js(aggiuntatmfSaveBlobe rasterizzazione SVG), testtests/js/test_image_viewer.js(TmfViewerMath + caricamento in node) + wrappertests/test_image_viewer_js.py, aggiornamentotests/js/test_save_image.js.operatore:templates/measure/task_execute.html,static/js/vision_execute.js: il riquadro immagine diventa<tmf-viewer>; via lo statoze le funzioni di zoom/pan;drawOverlay→$el.overlay;overlayLabels→labelsin px logici; Salva col nome del task. Test:tests/js/test_vision_zoom.js,tests/js/test_vision_rows.js,tests/test_vision_panel_fit.py,tests/test_vision_operatore.pyaggiornati.wizard:templates/maker/task_editor.html,static/js/dxf_recipe_wizard.js: anteprima immagine, anteprima DXF (modo contenuto, Salva PNG del disegno) e Prova (fit-height, overlay) diventano<tmf-viewer>; viaviews,vZoomBy,vFit,vWheel,vPan*,zoom,fit,zoomBy. Test:tests/js/test_dxf_zoom.js,tests/test_wizard_salva_svg.py,tests/test_dxf_recipe_routes.pyaggiornati.camera:scripts/calibrazione_pagina.py,scripts/camera_service.py: i tre visori #111 e l'immagine degli angoli diventano<tmf-viewer>conwidth/height= risoluzione della camera (A.ris);latoAnteprima()usaviewer-zoom/zoom; mirino (#mirino, #mirinoCal) dentro lo stage o sopra, allineato come oggi;camera_service.pyserveGET /visore/image_viewer.js,/visore/save_image.js,/visore/vision_overlay.jsleggendo i file dasrc/frontend/flask_app/static/js/(percorso risolto comeavvio.py,RADICE_SORGENTI; nel pacchetto Nuitka verificarepackaging/windows/build.ps1e dire come ci arrivano i file). Salva: frame pieno se un endpoint lo dà (save-src), altrimenti l'anteprima e lo si dice. Test:scripts/tests/(rotta che serve i JS, pagina che li include).TEST: ogni agente lancia node sui suoi test JS e pytest mirato dal worktree (
uv run --all-extras --with tzdata pytest -q <file>). Prova d'insieme in browser fatta da chi coordina dopo l'unione.NON TOCCARE:
static/js/vision_overlay.js(salvo bug dimostrato), backend, il disegno del calibro (annotation-viewer.js), le pagine maker non elencate, le miniature.CRITERIO DI FATTO: tutte le immagini elencate usano
<tmf-viewer>con la stessa barra; nessuna implementazione di zoom duplicata nei quattro file; Ctrl+rotella, pan, Adatta, Salva funzionano in Chrome su ognuna; overlay del pannello e della Prova allineati e a tratto costante; test verdi.🔵 daitarn — preso in carico 2026-10-28T20:53:02Z
ramo:
lavoro/daitarn· capacità: build-windows, gpuESITO: fatto, non deployato
RIF: gitea:Adriano/TieMeasureFlow#122 (con #118 #123 #124 #125)
COSA: quattro piani uniti su
lavoro/daitarn-tmf-dxfcal@5897a9e(pushato); prova in Chrome su pagine costruite dai template veri con Alpine 3.15.12 e i JS del ramo.FILE:
src/frontend/flask_app/static/js/image_viewer.js— regolatmf-viewer[hidden](l'host con hidden non si nascondeva); percentuale del modo contenuto (DXF in mm) relativa ad Adatta.src/frontend/flask_app/templates/maker/task_editor.html— Prova: un</div>in più chiudeva la griglia a due colonne (div 138/138 dopo la correzione).src/frontend/flask_app/tests/js/test_dxf_preview.js— via i controlli dello zoom del wizard,onImagePreviewLoad({width,height}).PROVA:
translations/*/messages.mo(file ignorato da git, compilato in locale: pagine in inglese, i test cercano l'italiano) → non legati al ramo.scripts/tests219 passed.MANCA: pagina camera 8765 non provata in browser (Studio occupa 8765 su daitarn; coperta da
scripts/tests); Salva della pagina camera scarica l'anteprima, non il frame pieno (nessun endpoint). Deploy sul PC fiera DESKTOP-RABEHJJ (irraggiungibile il 28/10 22:21), con ok di Adriano al riavvio.PROSSIMO: scp di
staging-118\(26 file + MANIFEST.sha256) inC:\Tielogic\backup\staging-118, poidep118.ps1(backup sorgenti e DB, copia, controllo hash, riavvio, controlli 8780/8765/8100); poiissue chiudidi #118-#125.🤖 goldrake — liberata nessun segno di daitarn da 6.3 h — 2026-10-29T03:08:06Z
Soluzione
Visore standard (zoom, Adatta, Salva, overlay, etichette) su pannello operatore, wizard DXF e pagine camera 8765 (JS serviti su /visore/). Commit
20c0a47d48a8a1815d49d269bbc084fc1a0c5be9a25897a9e. Prova in Chrome su wizard e pannello: issuecomment-16849. Ramo lavoro/daitarn-tmf-dxfcal @5897a9e. Deploy su TMF-FIERA (DESKTOP-RABEHJJ) 29/10/2026 08:53 con dep118.ps1: 26 file, hash SHA256 uguali al manifesto, 4 porte su in 24 s, worker ok, frontend 200; backup in C:\Tielogic\backup\pre-118. VERIFICATO sul PC fiera: /static/js/image_viewer.js (8780) e /visore/image_viewer.js (8765) rispondono 200 con lo stesso hash 141C9FCB9C96. Pagina camera non provata a mano nel browser.