fix: Image-Load robust + Schutz gegen Speichern ohne Bild
All checks were successful
Deploy bericht / deploy (push) Successful in 1s
All checks were successful
Deploy bericht / deploy (push) Successful in 1s
Diagnose: bei alten Berichten ohne composite_path wurde das bgImage beim Öffnen nicht geladen (fabric.Image.fromURL mit crossOrigin bei Blob-URL hing sich auf). Danach hat jeder Save das Composite ohne Bild gespeichert → PDF-Seite leer. Fix 1 — renderImage: - Native Image() statt fabric.Image.fromURL - Kein crossOrigin bei Blob-URLs (überflüssig, verursachte Hänger) - Timeout 10s mit console.warn - onerror mit Debug-Info (mime, bufSize) - applyTool() nach Einfügen damit Tool-Lock greift Fix 2 — savePageAnnotations: - Wenn KEIN Bild im Canvas ist (weder bgImage=true noch type=image), wird der Save ABGEBROCHEN statt ein leeres Composite zu speichern. - Dadurch kann ein alter Bericht nicht versehentlich mit einem leeren Composite überschrieben werden wenn der Bild-Load hakt. - Toast-Warnung für den User, console.warn für Debug. Fix 3 — finalize-Handler: - Native confirm() vor dem Finalisieren - Bessere Fehler-Diagnose: lese Response als text(), parse JSON, logge bei JSON-Parse-Fehler den Raw-Body - toast() statt alert() für Fehler Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> [deploy]
This commit is contained in:
parent
01fbd72310
commit
9c7ef73061
1 changed files with 80 additions and 38 deletions
68
js/editor.js
68
js/editor.js
|
|
@ -270,17 +270,24 @@
|
|||
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 htmlImg = new Image();
|
||||
const tid = setTimeout(() => {
|
||||
console.warn('[renderImage] Timeout beim Bild-Laden nach 10s — URL:', url);
|
||||
URL.revokeObjectURL(url);
|
||||
res();
|
||||
}, 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);
|
||||
|
||||
// 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({
|
||||
|
|
@ -292,13 +299,23 @@
|
|||
hasBorders: true,
|
||||
lockRotation: false,
|
||||
});
|
||||
// Ziehbares Hintergrundbild ganz nach hinten
|
||||
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();
|
||||
}, { crossOrigin: 'anonymous' });
|
||||
};
|
||||
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);
|
||||
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 data = await r.json();
|
||||
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);
|
||||
toast('✓ PDF erstellt: ' + data.filename);
|
||||
setTimeout(() => location.reload(), 1500);
|
||||
} else {
|
||||
alert('Fehler: ' + (data.error || 'unbekannt'));
|
||||
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');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue