diff --git a/src/main/java/de/assecutor/votianlt/pages/view/PDFBuilderView.java b/src/main/java/de/assecutor/votianlt/pages/view/PDFBuilderView.java index 0466fd0..0f6ecac 100644 --- a/src/main/java/de/assecutor/votianlt/pages/view/PDFBuilderView.java +++ b/src/main/java/de/assecutor/votianlt/pages/view/PDFBuilderView.java @@ -41,6 +41,7 @@ import com.vaadin.flow.component.textfield.NumberField; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import java.util.List; +import java.util.ArrayList; @Route(value = "pdf-builder", layout = MainLayout.class) @PageTitle("PDF Builder") @@ -53,6 +54,9 @@ public class PDFBuilderView extends Div { private final PdfTemplateService templateService; private final ObjectMapper objectMapper = new ObjectMapper(); private VerticalLayout inspectorList; + // Serverseitige Fallback-Liste der Frames (zeigt Einträge selbst dann an, + // wenn das DOM-Lesen via JS temporär fehlschlägt) + private final List serverFrames = new ArrayList<>(); public PDFBuilderView(PDFGenerationService pdfService, PdfTemplateService templateService) { this.pdfService = pdfService; @@ -124,7 +128,7 @@ public class PDFBuilderView extends Div { } private String getCollectStateJs() { - return "(function(){" + + return "return (function(){" + " const root = document.getElementById('pdf-canvas');\n" + " if(!root){ return JSON.stringify({items:[]}); }\n" + " root.classList.add('capturing');\n" + @@ -173,21 +177,49 @@ public class PDFBuilderView extends Div { UI.getCurrent().getPage().executeJs(js).then(String.class, json -> { try { String safeJson = (json == null || json.isBlank()) ? "[]" : json; + safeJson = safeJson.trim(); + if ("null".equalsIgnoreCase(safeJson) || "undefined".equalsIgnoreCase(safeJson)) { + safeJson = "[]"; + } List frames = objectMapper.readValue(safeJson, new TypeReference>(){}); - renderInspectorItems(frames); + // Erfolg: serverseitige Liste ersetzen und rendern + serverFrames.clear(); + if (frames != null) { serverFrames.addAll(frames); } + renderInspectorItems(serverFrames); } catch (Exception ex) { - Notification.show("Inspector-Update fehlgeschlagen: " + ex.getMessage(), 3000, Notification.Position.MIDDLE); + // Fallback: Letzten bekannten Stand aus serverFrames rendern + renderInspectorItems(serverFrames); + Notification.show("Inspector-Update fehlgeschlagen (Fallback genutzt): " + ex.getMessage(), 3000, Notification.Position.MIDDLE); } }); } + private void refreshInspectorFromDomDeferred() { + UI ui = UI.getCurrent(); + if (ui == null) { + refreshInspectorFromDom(); + return; + } + // Nach dem nächsten Paint (requestAnimationFrame) den Server anstoßen, + // damit der Inspector NACH den DOM-Updates liest. + ui.getPage().executeJs("requestAnimationFrame(() => { try { if ($0 && $0.$server && $0.$server.onAfterFrameMutation) { $0.$server.onAfterFrameMutation(); } } catch(e){} });", this.getElement()); + } + + @com.vaadin.flow.component.ClientCallable + private void onAfterFrameMutation() { + refreshInspectorFromDom(); + } + private void renderInspectorItems(List frames) { inspectorList.removeAll(); if (frames == null || frames.isEmpty()) return; for (FrameInfo f : frames) { + if (f == null) { continue; } Div row = new Div(); row.addClassName("inspector-item"); - Span label = new Span(("text".equals(f.type) ? "Text" : "Bild") + " (" + f.id + ")"); + String idText = (f.id == null ? "" : f.id); + String typeText = ("text".equals(f.type) ? "Text" : "Bild"); + Span label = new Span(typeText + " (" + idText + ")"); NumberField x = makeField("X", f.x); NumberField y = makeField("Y", f.y); NumberField w = makeField("Breite", f.width); @@ -219,15 +251,39 @@ public class PDFBuilderView extends Div { double dh = h == null ? 10 : Math.max(10, h); String js = "(function(){ const el = document.getElementById('" + id + "'); if(!el) return; el.style.left='" + (int)dx + "px'; el.style.top='" + (int)dy + "px'; el.style.width='" + (int)dw + "px'; el.style.height='" + (int)dh + "px'; })()"; UI.getCurrent().getPage().executeJs(js); - // Danach Inspector neu laden - refreshInspectorFromDom(); + // Serverliste aktualisieren + upsertServerFrame(id, null, (int)dx, (int)dy, (int)dw, (int)dh); + // Danach Inspector neu laden (deferred, damit Styles/DOM sicher angewendet sind) + refreshInspectorFromDomDeferred(); + } + + private void upsertServerFrame(String id, String type, Integer x, Integer y, Integer width, Integer height) { + if (id == null || id.isBlank()) return; + FrameInfo found = null; + for (FrameInfo fi : serverFrames) { if (fi != null && id.equals(fi.id)) { found = fi; break; } } + if (found == null) { + found = new FrameInfo(); + found.id = id; + serverFrames.add(found); + } + if (type != null) found.type = type; + if (x != null) found.x = x; + if (y != null) found.y = y; + if (width != null) found.width = width; + if (height != null) found.height = height; } private String getFramesInspectorJs(){ - return "(function(){\n" + + return "return (function(){\n" + " const root = document.getElementById('pdf-canvas');\n" + - " if(!root){ return '[]'; }\n" + - " const items = Array.from(root.querySelectorAll('.canvas-frame')).map(f=>{\n" + + " let frames = [];\n" + + " if (root) {\n" + + " frames = Array.from(root.querySelectorAll('.canvas-frame'));\n" + + " } else {\n" + + " // Fallback: global Suche (falls #pdf-canvas z.B. durch Slot/Shadow nicht direkt gefunden wird)\n" + + " frames = Array.from(document.querySelectorAll('#pdf-canvas .canvas-frame, .pdf-canvas .canvas-frame, .canvas-frame'));\n" + + " }\n" + + " const items = frames.map(f=>{\n" + " const cs = getComputedStyle(f);\n" + " const id = f.id || f.getAttribute('data-frame-id') || '';\n" + " const type = f.classList.contains('text-frame') ? 'text' : 'image';\n" + @@ -362,6 +418,15 @@ public class PDFBuilderView extends Div { enableCanvasDeactivation(inner); } + private void refreshInspectorAfterClientFlush() { + UI ui = UI.getCurrent(); + if (ui == null) { + PDFBuilderView.this.refreshInspectorFromDom(); + return; + } + ui.beforeClientResponse(PdfCanvas.this, ctx -> PDFBuilderView.this.refreshInspectorFromDom()); + } + private void enableDrop(Div target) { // Dragover/Drops auf dem Canvas erfassen und an Server melden target.getElement().executeJs( @@ -410,7 +475,7 @@ public class PDFBuilderView extends Div { @com.vaadin.flow.component.ClientCallable private void onFrameChanged() { // Nach Drag/Resize: Inspector-Seite aktualisieren - PDFBuilderView.this.refreshInspectorFromDom(); + PDFBuilderView.this.refreshInspectorFromDomDeferred(); } private void addTextFrame(int x, int y) { @@ -481,8 +546,11 @@ public class PDFBuilderView extends Div { enableFrameDragging(frame); // Aktivierungsverhalten enableFrameActivation(frame); - // Inspector aktualisieren - PDFBuilderView.this.refreshInspectorFromDom(); + // Serverliste sofort updaten und rendern, damit der Eintrag direkt erscheint + upsertServerFrame(id, "text", x, y, 300, 140); + renderInspectorItems(serverFrames); + // Zusätzlich clientseitig deferred refresher, um DOM-Werte zu synchronisieren + PDFBuilderView.this.refreshInspectorFromDomDeferred(); } private void promptImageUpload(int x, int y) { @@ -542,8 +610,11 @@ public class PDFBuilderView extends Div { enableFrameDragging(frame); // Aktivierungsverhalten enableFrameActivation(frame); - // Inspector aktualisieren - PDFBuilderView.this.refreshInspectorFromDom(); + // Serverliste sofort updaten und rendern + upsertServerFrame(id, "image", x, y, 260, 180); + renderInspectorItems(serverFrames); + // Zusätzlich deferred refresher + PDFBuilderView.this.refreshInspectorFromDomDeferred(); } private void enableFrameDragging(Div frame) {