diff --git a/js/editor.js b/js/editor.js index 24c89be..b0eaf2c 100644 --- a/js/editor.js +++ b/js/editor.js @@ -270,35 +270,52 @@ const blob = new Blob([arrayBuffer], { type: mime }); const url = URL.createObjectURL(blob); + // Native Image-Load + fabric.Image(element) — kein CORS auf Blob-URLs, + // bessere Fehler-Diagnose, Timeout-Fallback return new Promise((res) => { - fabric.Image.fromURL(url, (fabricImg) => { - // Wenn schon ein Bild-Objekt im Canvas ist (nach re-render), - // altes entfernen bevor neues rein kommt - const existing = fabricCanvas.getObjects().find(o => o.bgImage === true); - if (existing) fabricCanvas.remove(existing); - - // Markiere dieses Objekt als "Hintergrundbild" (bleibt aber editierbar) - fabricImg.bgImage = true; - - // Auf Canvas-Größe skalieren (contain) - const imgRatio = Math.min(canvasW / fabricImg.width, canvasH / fabricImg.height); - fabricImg.scale(imgRatio); - fabricImg.set({ - left: (canvasW - fabricImg.width * imgRatio) / 2, - top: (canvasH - fabricImg.height * imgRatio) / 2, - angle: currentPageRotation, - selectable: true, - hasControls: true, - hasBorders: true, - lockRotation: false, - }); - // Ziehbares Hintergrundbild ganz nach hinten - fabricCanvas.add(fabricImg); - fabricImg.sendToBack(); - fabricCanvas.requestRenderAll(); + const htmlImg = new Image(); + const tid = setTimeout(() => { + console.warn('[renderImage] Timeout beim Bild-Laden nach 10s — URL:', url); URL.revokeObjectURL(url); res(); - }, { crossOrigin: 'anonymous' }); + }, 10000); + + htmlImg.onload = () => { + clearTimeout(tid); + try { + const fabricImg = new fabric.Image(htmlImg); + const existing = fabricCanvas.getObjects().find(o => o.bgImage === true); + if (existing) fabricCanvas.remove(existing); + + fabricImg.bgImage = true; + const imgRatio = Math.min(canvasW / fabricImg.width, canvasH / fabricImg.height); + fabricImg.scale(imgRatio); + fabricImg.set({ + left: (canvasW - fabricImg.width * imgRatio) / 2, + top: (canvasH - fabricImg.height * imgRatio) / 2, + angle: currentPageRotation, + selectable: true, + hasControls: true, + hasBorders: true, + lockRotation: false, + }); + fabricCanvas.add(fabricImg); + fabricImg.sendToBack(); + fabricCanvas.requestRenderAll(); + if (typeof applyTool === 'function') applyTool(); + } catch (e) { + console.error('[renderImage] Fabric-Wrap fehlgeschlagen:', e); + } + URL.revokeObjectURL(url); + res(); + }; + htmlImg.onerror = (err) => { + clearTimeout(tid); + console.error('[renderImage] htmlImg onerror:', err, 'mime:', mime, 'bufSize:', arrayBuffer.byteLength); + URL.revokeObjectURL(url); + res(); + }; + htmlImg.src = url; }); } @@ -713,15 +730,25 @@ /* ---------- Speichern ---------- */ async function savePageAnnotations(showMessage = true) { if (!currentPageId) return; + + // Sicherheit: wenn kein bgImage im Canvas ist, NICHT speichern — sonst + // überschreiben wir einen funktionierenden Bericht mit einem leeren + // Composite + const hasBg = fabricCanvas.getObjects().some(o => o.bgImage === true || o.type === 'image'); + if (!hasBg) { + console.warn('[savePageAnnotations] Kein Bild im Canvas — skip, um Datenverlust zu vermeiden'); + if (showMessage) toast('Speichern übersprungen (kein Bild geladen)', 'warn'); + return; + } + const fd = new FormData(); fd.append('token', cfg.token); fd.append('pageid', currentPageId); - // Fabric-JSON MIT Hintergrundbild (bgImage-Objekte werden serialisiert) fd.append('fabric_json', JSON.stringify(fabricCanvas.toJSON(['bgImage']))); fd.append('note', document.getElementById('page-note').value || ''); fd.append('rotation', currentPageRotation); - // Phase 6: Composite-PNG der gesamten Seite generieren und mitschicken + // Phase 6: Composite-PNG try { // Selektion aufheben damit keine Controls gerendert werden const active = fabricCanvas.getActiveObject(); @@ -821,17 +848,32 @@ } document.getElementById('btn-finalize').addEventListener('click', async () => { + if (!confirm('Bericht jetzt finalisieren und PDF erzeugen?')) return; + toast('Speichere aktuelle Seite…'); await savePageAnnotations(false); - const fd = new FormData(); - fd.append('token', cfg.token); - fd.append('berichtid', cfg.berichtid); - const r = await fetch(cfg.urls.generate_pdf, { method: 'POST', body: fd }); - const data = await r.json(); - if (data.success) { - toast('PDF erstellt: ' + data.filename); - setTimeout(() => location.reload(), 1500); - } else { - alert('Fehler: ' + (data.error || 'unbekannt')); + toast('PDF wird erzeugt…'); + try { + const fd = new FormData(); + fd.append('token', cfg.token); + fd.append('berichtid', cfg.berichtid); + const r = await fetch(cfg.urls.generate_pdf, { method: 'POST', body: fd }); + const txt = await r.text(); + let data = {}; + try { data = JSON.parse(txt); } catch (e) { + console.error('generate_pdf lieferte kein JSON:', txt); + toast('Server-Fehler (kein JSON)', 'error'); + return; + } + if (data.success) { + toast('✓ PDF erstellt: ' + data.filename); + setTimeout(() => location.reload(), 1500); + } else { + console.error('generate_pdf failed:', data); + toast('Fehler: ' + (data.error || 'unbekannt'), 'error'); + } + } catch (e) { + console.error('finalize exception:', e); + toast('Netzwerk-Fehler: ' + e.message, 'error'); } }); }