fix: file display, persistence, PDF support and save error handling

- Add file proxy route in maker blueprint (X-API-Key auth for browser requests)
- Persist file_path/annotations_json to DB via RecipeCreate/RecipeUpdate schemas
- Fix canvas sizing using grandparent container instead of Fabric.js wrapper div
- Defer canvas init with requestAnimationFrame for x-show timing
- Add PDF.js support in annotation-editor and annotation-viewer
- Fix annotations_json double-serialization (parse string to object before send)
- Handle FastAPI 422 validation error arrays in api_client and JS error display
- Update template URLs to use /maker/api/files/ proxy path

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Adriano
2026-02-07 22:04:45 +01:00
parent d262ef68af
commit b075115cef
8 changed files with 315 additions and 65 deletions
+73 -30
View File
@@ -80,47 +80,90 @@ function annotationViewer() {
},
/**
* Carica e renderizza l'immagine con scaling
* Carica e renderizza l'immagine (o la prima pagina PDF) con scaling
*/
loadImage() {
var isPdf = this.imageUrl.toLowerCase().replace(/\?.*$/, '').endsWith('.pdf');
if (isPdf && typeof pdfjsLib !== 'undefined') {
this._loadPdf();
} else {
this._loadRasterImage();
}
},
/**
* Render first page of a PDF via PDF.js
*/
_loadPdf() {
const self = this;
pdfjsLib.getDocument(this.imageUrl).promise.then(function (pdf) {
return pdf.getPage(1);
}).then(function (page) {
const pdfScale = 2; // render at 2x for clarity
const viewport = page.getViewport({ scale: pdfScale });
const offCanvas = document.createElement('canvas');
offCanvas.width = viewport.width;
offCanvas.height = viewport.height;
const offCtx = offCanvas.getContext('2d');
page.render({ canvasContext: offCtx, viewport: viewport }).promise.then(function () {
// Use rendered PDF page as if it were an image
self._drawImageOnCanvas(offCanvas, viewport.width, viewport.height);
});
}).catch(function (err) {
console.error('AnnotationViewer: failed to load PDF:', err);
});
},
/**
* Load a raster image (PNG, JPG, etc.)
*/
_loadRasterImage() {
const self = this;
const img = new Image();
img.onload = () => {
// Calculate scale to fit container
const container = this.canvas.parentElement;
if (!container) {
console.error('AnnotationViewer: parent container not found');
return;
}
const containerWidth = container.clientWidth;
const containerHeight = container.clientHeight || 500; // fallback height
this.scale = Math.min(
containerWidth / img.width,
containerHeight / img.height,
1 // non ingrandire oltre dimensione originale
);
this.canvas.width = img.width * this.scale;
this.canvas.height = img.height * this.scale;
// Draw image
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
this.ctx.drawImage(img, 0, 0, this.canvas.width, this.canvas.height);
this.imageLoaded = true;
// Draw annotations on top
this.drawAnnotations();
img.onload = function () {
self._drawImageOnCanvas(img, img.width, img.height);
};
img.onerror = () => {
console.error('AnnotationViewer: failed to load image:', this.imageUrl);
img.onerror = function () {
console.error('AnnotationViewer: failed to load image:', self.imageUrl);
};
img.src = this.imageUrl;
},
/**
* Draw an image source (Image or Canvas) scaled to fit the container
*/
_drawImageOnCanvas(source, srcWidth, srcHeight) {
const container = this.canvas.parentElement;
if (!container) {
console.error('AnnotationViewer: parent container not found');
return;
}
const containerWidth = container.clientWidth;
const containerHeight = container.clientHeight || 500;
this.scale = Math.min(
containerWidth / srcWidth,
containerHeight / srcHeight,
1
);
this.canvas.width = srcWidth * this.scale;
this.canvas.height = srcHeight * this.scale;
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
this.ctx.drawImage(source, 0, 0, this.canvas.width, this.canvas.height);
this.imageLoaded = true;
this.drawAnnotations();
},
/**
* Disegna tutti i markers sulle annotazioni
*/