From b9c767fb0c4d69c2c84c1221e604aea2367b0570 Mon Sep 17 00:00:00 2001 From: Adriano Dal Pastro Date: Sat, 23 May 2026 13:26:23 +0000 Subject: [PATCH] =?UTF-8?q?feat(ui):=20rinomina=20Misure=E2=86=92Attivit?= =?UTF-8?q?=C3=A0,=20textarea=20descrizione=20task,=20tool=20Testo=20annot?= =?UTF-8?q?azioni?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Navbar e breadcrumb: label "Misure" rinominata "Attività" (IT) / "Activities" (EN) - Descrizione task: input sostituito con textarea multilinea + display whitespace-pre-wrap - Annotation editor: nuovo tool "Testo" con fabric.IText (inline editing, serializzazione, viewer) Co-Authored-By: Claude Opus 4.7 (1M context) --- .../flask_app/static/js/annotation-editor.js | 68 ++++++++++++++++++- .../flask_app/static/js/annotation-viewer.js | 20 ++++++ .../templates/maker/task_drawing.html | 12 ++++ .../templates/maker/task_editor.html | 22 +++--- .../templates/measure/task_list.html | 2 +- .../translations/en/LC_MESSAGES/messages.po | 8 ++- .../translations/it/LC_MESSAGES/messages.po | 8 ++- 7 files changed, 123 insertions(+), 17 deletions(-) diff --git a/src/frontend/flask_app/static/js/annotation-editor.js b/src/frontend/flask_app/static/js/annotation-editor.js index 669fc86..27cfd5d 100644 --- a/src/frontend/flask_app/static/js/annotation-editor.js +++ b/src/frontend/flask_app/static/js/annotation-editor.js @@ -671,6 +671,53 @@ function annotationEditor() { return rect; }, + // ---------------------------------------------------------------- + // Object creation - Text + // ---------------------------------------------------------------- + + addText(x, y, textContent, fontSize) { + var size = fontSize || 18; + var itext = new fabric.IText(textContent || 'Testo', { + left: x, + top: y, + fontSize: size, + fill: this.currentColor, + fontFamily: 'Inter, sans-serif', + fontWeight: 'bold', + selectable: true, + evented: true, + hasControls: false, + hasBorders: true, + lockRotation: true, + lockScalingX: true, + lockScalingY: true, + objectType: 'text', + textColor: this.currentColor, + textFontSize: size, + }); + + var self = this; + itext.on('editing:exited', function () { + if (!itext.text || itext.text.trim() === '') { + self.canvas.remove(itext); + } + itext.textColor = itext.fill; + self.isDirty = true; + window.dispatchEvent(new CustomEvent('annotations-changed', { detail: { json: self.getAnnotationsJson() } })); + }); + + itext.on('changed', function () { + self.isDirty = true; + }); + + this.canvas.add(itext); + itext.setCoords(); + this.canvas.setActiveObject(itext); + this.isDirty = true; + window.dispatchEvent(new CustomEvent('annotations-changed', { detail: { json: this.getAnnotationsJson() } })); + return itext; + }, + // ---------------------------------------------------------------- // Toolbar / mode switching // ---------------------------------------------------------------- @@ -678,7 +725,7 @@ function annotationEditor() { /** * Set the current interaction mode. * - * @param {'select'|'marker'|'arrow'|'rect'} mode + * @param {'select'|'marker'|'arrow'|'rect'|'text'} mode */ setMode(mode) { this.activeMode = mode; @@ -731,6 +778,13 @@ function annotationEditor() { self.addMarker(pointer.x, pointer.y); self.setMode('select'); window.dispatchEvent(new CustomEvent('anno-mode-changed', { detail: { mode: 'select' } })); + } else if (self.activeMode === 'text') { + var textObj = self.addText(pointer.x, pointer.y); + self.setMode('select'); + window.dispatchEvent(new CustomEvent('anno-mode-changed', { detail: { mode: 'select' } })); + self.canvas.setActiveObject(textObj); + textObj.enterEditing(); + textObj.selectAll(); } else if (self.activeMode === 'arrow') { isDrawing = true; drawStartX = pointer.x; @@ -1023,6 +1077,13 @@ function annotationEditor() { if (obj.arrowLineDash) data.lineDash = obj.arrowLineDash; if (obj.areaLineDash) data.lineDash = obj.areaLineDash; + // Text-specific + if (obj.objectType === 'text') { + data.textContent = obj.text; + data.textColor = obj.textColor || obj.fill; + data.fontSize = obj.textFontSize || obj.fontSize; + } + // Arrow-specific: store original endpoints if (obj.objectType === 'arrow') { data.x1 = obj.arrowX1 != null ? Math.round(obj.arrowX1 * 100) / 100 : null; @@ -1120,6 +1181,11 @@ function annotationEditor() { this.currentColor = savedColor3; this.currentStrokeWidth = savedWidth2; this.currentLineDash = savedDash2; + } else if (obj.type === 'text') { + var savedColor4 = this.currentColor; + if (obj.textColor) this.currentColor = obj.textColor; + this.addText(obj.left * cs, obj.top * cs, obj.textContent, (obj.fontSize || 18) * cs); + this.currentColor = savedColor4; } } diff --git a/src/frontend/flask_app/static/js/annotation-viewer.js b/src/frontend/flask_app/static/js/annotation-viewer.js index eff5877..f50715f 100644 --- a/src/frontend/flask_app/static/js/annotation-viewer.js +++ b/src/frontend/flask_app/static/js/annotation-viewer.js @@ -436,6 +436,8 @@ function _drawEditorAnnotations(ctx, data, scale) { _drawPreviewArrow(ctx, obj, annoScale); } else if (obj.type === 'area') { _drawPreviewArea(ctx, obj, annoScale); + } else if (obj.type === 'text') { + _drawPreviewText(ctx, obj, annoScale); } } } @@ -514,6 +516,24 @@ function _drawPreviewArea(ctx, obj, scale) { ctx.setLineDash([]); } +function _drawPreviewText(ctx, obj, scale) { + var x = obj.left * scale; + var y = obj.top * scale; + var color = obj.textColor || '#000000'; + var fontSize = Math.max(10, Math.round((obj.fontSize || 18) * scale)); + + ctx.fillStyle = color; + ctx.font = 'bold ' + fontSize + 'px Inter, sans-serif'; + ctx.textAlign = 'left'; + ctx.textBaseline = 'top'; + + var lines = (obj.textContent || '').split('\n'); + var lineHeight = fontSize * 1.2; + for (var j = 0; j < lines.length; j++) { + ctx.fillText(lines[j], x, y + j * lineHeight); + } +} + /** * Draw annotations in the legacy format (markers: point/rectangle). */ diff --git a/src/frontend/flask_app/templates/maker/task_drawing.html b/src/frontend/flask_app/templates/maker/task_drawing.html index c51a7a9..a824fdc 100644 --- a/src/frontend/flask_app/templates/maker/task_drawing.html +++ b/src/frontend/flask_app/templates/maker/task_drawing.html @@ -247,6 +247,18 @@ + + +
diff --git a/src/frontend/flask_app/templates/maker/task_editor.html b/src/frontend/flask_app/templates/maker/task_editor.html index 6897f15..8ab8b69 100644 --- a/src/frontend/flask_app/templates/maker/task_editor.html +++ b/src/frontend/flask_app/templates/maker/task_editor.html @@ -269,12 +269,12 @@ placeholder="{{ _('Es. Seguire procedura ISO 2768') }}"> -
+
- +
@@ -497,7 +497,7 @@ @@ -513,12 +513,12 @@ class="tmf-input text-sm" placeholder="{{ _('Direttiva opzionale...') }}"> -
+
- +
diff --git a/src/frontend/flask_app/templates/measure/task_list.html b/src/frontend/flask_app/templates/measure/task_list.html index 095a2ff..57740ae 100644 --- a/src/frontend/flask_app/templates/measure/task_list.html +++ b/src/frontend/flask_app/templates/measure/task_list.html @@ -134,7 +134,7 @@ {% if task.directive or task.description %} -

+

{{ task.directive or task.description }}

{% endif %} diff --git a/src/frontend/flask_app/translations/en/LC_MESSAGES/messages.po b/src/frontend/flask_app/translations/en/LC_MESSAGES/messages.po index c5b2178..7056f84 100644 --- a/src/frontend/flask_app/translations/en/LC_MESSAGES/messages.po +++ b/src/frontend/flask_app/translations/en/LC_MESSAGES/messages.po @@ -650,7 +650,7 @@ msgstr "NON-CONFORMING" #: templates/measure/task_complete.html:16 #: templates/measure/task_execute.html:2 templates/measure/task_list.html:16 msgid "Misure" -msgstr "Measurements" +msgstr "Activities" #: templates/components/navbar.html:55 templates/components/navbar.html:265 #: templates/maker/recipe_editor.html:51 templates/maker/recipe_list.html:83 @@ -1069,7 +1069,11 @@ msgstr "Arrow" msgid "Rettangolo" msgstr "Rectangle" -#: templates/maker/task_drawing.html:258 +#: templates/maker/task_drawing.html:261 +msgid "Testo" +msgstr "Text" + +#: templates/maker/task_drawing.html:272 msgid "Elimina selezionato" msgstr "Delete selected" diff --git a/src/frontend/flask_app/translations/it/LC_MESSAGES/messages.po b/src/frontend/flask_app/translations/it/LC_MESSAGES/messages.po index b438cce..1e8a501 100644 --- a/src/frontend/flask_app/translations/it/LC_MESSAGES/messages.po +++ b/src/frontend/flask_app/translations/it/LC_MESSAGES/messages.po @@ -680,7 +680,7 @@ msgstr "NON CONFORME" #: templates/measure/task_complete.html:16 #: templates/measure/task_execute.html:2 templates/measure/task_list.html:16 msgid "Misure" -msgstr "Misure" +msgstr "Attività" #: templates/components/navbar.html:55 templates/components/navbar.html:265 #: templates/maker/recipe_editor.html:51 templates/maker/recipe_list.html:83 @@ -1112,7 +1112,11 @@ msgstr "Freccia" msgid "Rettangolo" msgstr "Rettangolo" -#: templates/maker/task_drawing.html:258 +#: templates/maker/task_drawing.html:261 +msgid "Testo" +msgstr "Testo" + +#: templates/maker/task_drawing.html:272 msgid "Elimina selezionato" msgstr "Elimina selezionato"