feat(ui): rinomina Misure→Attività, textarea descrizione task, tool Testo annotazioni
- 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) <noreply@anthropic.com>
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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).
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user