Erweiterungen

This commit is contained in:
2025-08-30 19:06:19 +02:00
parent 60febd6771
commit 9f4bb02ac5

View File

@@ -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<FrameInfo> 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<FrameInfo> frames = objectMapper.readValue(safeJson, new TypeReference<List<FrameInfo>>(){});
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<FrameInfo> 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) {