Erweiterungen

This commit is contained in:
2026-02-13 12:55:20 +01:00
parent 2af9c9a99a
commit b80eb53300
4 changed files with 77 additions and 46 deletions

Binary file not shown.

View File

@@ -48,14 +48,23 @@ window.initProfileInvoiceGenerator = function() {
// Page dimensions // Page dimensions
var padding = 10; var padding = 10;
var pageX, pageY, pageWidth, pageHeight; var pageX, pageY, pageWidth, pageHeight;
var zoomFactor = 1;
var basePageWidth = 595; // A4 width in pixels at 96 DPI
var basePageHeight = 842; // A4 height in pixels at 96 DPI
function updatePageDimensions() { function updatePageDimensions() {
var w = canvas.width; var w = canvas.width;
var h = canvas.height; var h = canvas.height;
var availableWidth = w - padding * 2; var availableWidth = w - padding * 2;
var availableHeight = h - padding * 2; var availableHeight = h - padding * 2;
pageWidth = Math.min(availableWidth, availableHeight * 0.707);
pageHeight = pageWidth / 0.707; // Calculate zoom factor based on available space
var zoomX = availableWidth / basePageWidth;
var zoomY = availableHeight / basePageHeight;
zoomFactor = Math.min(zoomX, zoomY);
pageWidth = basePageWidth * zoomFactor;
pageHeight = basePageHeight * zoomFactor;
pageX = (w - pageWidth) / 2; pageX = (w - pageWidth) / 2;
pageY = (h - pageHeight) / 2; pageY = (h - pageHeight) / 2;
} }
@@ -111,16 +120,17 @@ window.initProfileInvoiceGenerator = function() {
ctx.lineWidth = 1; ctx.lineWidth = 1;
ctx.strokeRect(pageX, pageY, pageWidth, pageHeight); ctx.strokeRect(pageX, pageY, pageWidth, pageHeight);
// Grid // Grid (scaled by zoom factor)
ctx.strokeStyle = 'rgba(200, 200, 200, 0.3)'; ctx.strokeStyle = 'rgba(200, 200, 200, 0.3)';
ctx.lineWidth = 0.5; ctx.lineWidth = 0.5;
for (var x = pageX; x <= pageX + pageWidth; x += gridSize) { var scaledGridSize = gridSize * zoomFactor;
for (var x = pageX; x <= pageX + pageWidth; x += scaledGridSize) {
ctx.beginPath(); ctx.beginPath();
ctx.moveTo(x, pageY); ctx.moveTo(x, pageY);
ctx.lineTo(x, pageY + pageHeight); ctx.lineTo(x, pageY + pageHeight);
ctx.stroke(); ctx.stroke();
} }
for (var y = pageY; y <= pageY + pageHeight; y += gridSize) { for (var y = pageY; y <= pageY + pageHeight; y += scaledGridSize) {
ctx.beginPath(); ctx.beginPath();
ctx.moveTo(pageX, y); ctx.moveTo(pageX, y);
ctx.lineTo(pageX + pageWidth, y); ctx.lineTo(pageX + pageWidth, y);
@@ -141,14 +151,16 @@ window.initProfileInvoiceGenerator = function() {
function drawElement(el) { function drawElement(el) {
ctx.save(); ctx.save();
var x = pageX + el.x; // Apply zoom factor to position and size
var y = pageY + el.y; var x = pageX + (el.x * zoomFactor);
var w = el.width || 100; var y = pageY + (el.y * zoomFactor);
var h = el.height || 30; var w = (el.width || 100) * zoomFactor;
var h = (el.height || 30) * zoomFactor;
var fontSize = (el.fontSize || 14) * zoomFactor;
if (el.type === 'line') { if (el.type === 'line') {
ctx.strokeStyle = el.color || '#333333'; ctx.strokeStyle = el.color || '#333333';
ctx.lineWidth = 1; ctx.lineWidth = Math.max(1, zoomFactor);
ctx.beginPath(); ctx.beginPath();
ctx.moveTo(x, y); ctx.moveTo(x, y);
ctx.lineTo(x + w, y); ctx.lineTo(x + w, y);
@@ -159,29 +171,32 @@ window.initProfileInvoiceGenerator = function() {
ctx.strokeStyle = '#999999'; ctx.strokeStyle = '#999999';
ctx.strokeRect(x, y, w, h); ctx.strokeRect(x, y, w, h);
ctx.fillStyle = '#666666'; ctx.fillStyle = '#666666';
ctx.font = '12px Arial'; ctx.font = Math.max(8, 12 * zoomFactor) + 'px Arial';
ctx.textAlign = 'center'; ctx.textAlign = 'center';
ctx.textBaseline = 'middle'; ctx.textBaseline = 'middle';
ctx.fillText('Bild', x + w / 2, y + h / 2); ctx.fillText('Bild', x + w / 2, y + h / 2);
} else { } else {
// Text elements // Text elements
ctx.font = (el.fontStyle || '') + ' ' + (el.fontSize || 14) + 'px Arial'; ctx.font = (el.fontStyle || '') + ' ' + fontSize + 'px Arial';
ctx.fillStyle = el.color || '#333333'; ctx.fillStyle = el.color || '#333333';
ctx.textBaseline = 'top';
var lines = (el.text || '').split('\n');
var lineHeight = fontSize * 1.2;
var totalTextHeight = lines.length * lineHeight;
// Vertically center the text in the element
var ty = y + (h - totalTextHeight) / 2;
// Draw background highlight for static elements // Draw background highlight for static elements
if (el.isStatic) { if (el.isStatic) {
var textWidth = ctx.measureText(el.text || '').width + 10; var textWidth = ctx.measureText(el.text || '').width + (10 * zoomFactor);
ctx.fillStyle = 'rgba(25, 118, 210, 0.1)'; // Light blue background ctx.fillStyle = 'rgba(25, 118, 210, 0.1)'; // Light blue background
ctx.fillRect(x - 3, y - 2, Math.max(w, textWidth), h); ctx.fillRect(x - (3 * zoomFactor), y - (2 * zoomFactor), Math.max(w, textWidth), h);
} }
ctx.fillStyle = el.color || '#333333'; ctx.fillStyle = el.color || '#333333';
ctx.font = (el.fontStyle || '') + ' ' + (el.fontSize || 14) + 'px Arial'; ctx.font = (el.fontStyle || '') + ' ' + fontSize + 'px Arial';
ctx.textBaseline = 'top';
var lines = (el.text || '').split('\n');
var lineHeight = (el.fontSize || 14) * 1.2;
var ty = y;
lines.forEach(function(line) { lines.forEach(function(line) {
ctx.fillText(line, x, ty); ctx.fillText(line, x, ty);
@@ -193,22 +208,27 @@ window.initProfileInvoiceGenerator = function() {
} }
function drawSelection(el) { function drawSelection(el) {
var x = pageX + el.x; var x = pageX + (el.x * zoomFactor);
var y = pageY + el.y; var y = pageY + (el.y * zoomFactor);
var w = el.width || 100; var w = (el.width || 100) * zoomFactor;
var h = el.height || 30; var h = (el.height || 30) * zoomFactor;
var hs = 8; // handle size
ctx.strokeStyle = '#1976d2'; ctx.strokeStyle = '#1976d2';
ctx.lineWidth = 2; ctx.lineWidth = Math.max(1, 2 * zoomFactor);
ctx.setLineDash([5, 3]); ctx.setLineDash([5, 3]);
ctx.strokeRect(x, y, w, h); ctx.strokeRect(x, y, w, h);
ctx.setLineDash([]); ctx.setLineDash([]);
// Don't show resize handles for static elements
if (el.isStatic) {
return;
}
// Handles // Handles
var hs = Math.max(6, 8 * zoomFactor); // handle size scaled
ctx.fillStyle = '#ffffff'; ctx.fillStyle = '#ffffff';
ctx.strokeStyle = '#1976d2'; ctx.strokeStyle = '#1976d2';
ctx.lineWidth = 2; ctx.lineWidth = Math.max(1, 2 * zoomFactor);
var positions = [ var positions = [
[x - hs/2, y - hs/2], [x - hs/2, y - hs/2],
@@ -228,10 +248,10 @@ window.initProfileInvoiceGenerator = function() {
} }
function hitTest(x, y, el) { function hitTest(x, y, el) {
var ex = pageX + el.x; var ex = pageX + (el.x * zoomFactor);
var ey = pageY + el.y; var ey = pageY + (el.y * zoomFactor);
var ew = el.width || 100; var ew = (el.width || 100) * zoomFactor;
var eh = el.height || 30; var eh = (el.height || 30) * zoomFactor;
return x >= ex && x <= ex + ew && y >= ey && y <= ey + eh; return x >= ex && x <= ex + ew && y >= ey && y <= ey + eh;
} }
@@ -271,15 +291,16 @@ window.initProfileInvoiceGenerator = function() {
var y = e.clientY - rect.top; var y = e.clientY - rect.top;
if (isDragging && selectedElement) { if (isDragging && selectedElement) {
var dx = x - dragStart.x; // Adjust mouse movement by zoom factor to get stored coordinates
var dy = y - dragStart.y; var dx = (x - dragStart.x) / zoomFactor;
var dy = (y - dragStart.y) / zoomFactor;
var newX = elementStart.x + dx; var newX = elementStart.x + dx;
var newY = elementStart.y + dy; var newY = elementStart.y + dy;
// Constrain to page // Constrain to page (using base coordinates, not zoomed)
newX = Math.max(0, Math.min(newX, pageWidth - (selectedElement.width || 100))); newX = Math.max(0, Math.min(newX, basePageWidth - (selectedElement.width || 100)));
newY = Math.max(0, Math.min(newY, pageHeight - (selectedElement.height || 30))); newY = Math.max(0, Math.min(newY, basePageHeight - (selectedElement.height || 30)));
// Snap to grid // Snap to grid
selectedElement.x = snapToGrid(newX); selectedElement.x = snapToGrid(newX);
@@ -325,7 +346,7 @@ window.initProfileInvoiceGenerator = function() {
e.preventDefault(); e.preventDefault();
break; break;
case 'ArrowDown': case 'ArrowDown':
selectedElement.y = Math.min(pageHeight - (selectedElement.height || 30), selectedElement.y + step); selectedElement.y = Math.min(basePageHeight - (selectedElement.height || 30), selectedElement.y + step);
moved = true; moved = true;
e.preventDefault(); e.preventDefault();
break; break;
@@ -335,7 +356,7 @@ window.initProfileInvoiceGenerator = function() {
e.preventDefault(); e.preventDefault();
break; break;
case 'ArrowRight': case 'ArrowRight':
selectedElement.x = Math.min(pageWidth - (selectedElement.width || 100), selectedElement.x + step); selectedElement.x = Math.min(basePageWidth - (selectedElement.width || 100), selectedElement.x + step);
moved = true; moved = true;
e.preventDefault(); e.preventDefault();
break; break;
@@ -362,9 +383,9 @@ window.initProfileInvoiceGenerator = function() {
elementCounter++; elementCounter++;
var id = 'element-' + elementCounter; var id = 'element-' + elementCounter;
// Convert to page coordinates // Convert to page coordinates (adjusting for zoom factor)
var x = dropX - pageX - 50; var x = (dropX - pageX - 50 * zoomFactor) / zoomFactor;
var y = dropY - pageY - 15; var y = (dropY - pageY - 15 * zoomFactor) / zoomFactor;
x = Math.max(10, x); x = Math.max(10, x);
y = Math.max(10, y); y = Math.max(10, y);
x = snapToGrid(x); x = snapToGrid(x);

View File

@@ -389,7 +389,13 @@ public class EditProfileView extends HorizontalLayout {
return; return;
} }
try { try {
String templateData = result.toString(); String templateData;
if (result instanceof elemental.json.JsonValue) {
elemental.json.JsonValue jsonValue = (elemental.json.JsonValue) result;
templateData = jsonValue.toJson();
} else {
templateData = result.toString();
}
invoiceTemplateService.saveTemplate(currentUser.getId().toString(), templateData); invoiceTemplateService.saveTemplate(currentUser.getId().toString(), templateData);
Notification.show("Template erfolgreich gespeichert", 3000, Notification.Position.BOTTOM_CENTER); Notification.show("Template erfolgreich gespeichert", 3000, Notification.Position.BOTTOM_CENTER);
} catch (Exception ex) { } catch (Exception ex) {

View File

@@ -261,8 +261,8 @@ public class CustomerInvoiceService {
htmlBuilder.append("<style>"); htmlBuilder.append("<style>");
htmlBuilder.append("@page { size: A4; margin: 0; }"); htmlBuilder.append("@page { size: A4; margin: 0; }");
htmlBuilder.append("body { margin: 0; padding: 0; width: 210mm; height: 297mm; position: relative; font-family: Arial, sans-serif; }"); htmlBuilder.append("body { margin: 0; padding: 0; width: 210mm; height: 297mm; position: relative; font-family: Arial, sans-serif; }");
htmlBuilder.append(".element { position: absolute; }"); htmlBuilder.append(".element { position: absolute; box-sizing: border-box; overflow: hidden; }");
htmlBuilder.append(".text { white-space: pre; }"); htmlBuilder.append(".text { white-space: nowrap; overflow: visible; }");
htmlBuilder.append(".line { border-top: 1px solid #333; }"); htmlBuilder.append(".line { border-top: 1px solid #333; }");
htmlBuilder.append(".image { max-width: 100%; max-height: 100%; }"); htmlBuilder.append(".image { max-width: 100%; max-height: 100%; }");
htmlBuilder.append("</style>"); htmlBuilder.append("</style>");
@@ -293,10 +293,13 @@ public class CustomerInvoiceService {
htmlBuilder.append("width:").append(String.format(java.util.Locale.US, "%.2f", mmWidth)).append("mm;"); htmlBuilder.append("width:").append(String.format(java.util.Locale.US, "%.2f", mmWidth)).append("mm;");
htmlBuilder.append("height:").append(String.format(java.util.Locale.US, "%.2f", mmHeight)).append("mm;"); htmlBuilder.append("height:").append(String.format(java.util.Locale.US, "%.2f", mmHeight)).append("mm;");
htmlBuilder.append("font-size:").append(fontSize).append("pt;"); htmlBuilder.append("font-size:").append(fontSize).append("pt;");
htmlBuilder.append("line-height:").append(String.format(java.util.Locale.US, "%.2f", fontSize * 1.2)).append("pt;");
htmlBuilder.append("color:").append(color).append(";"); htmlBuilder.append("color:").append(color).append(";");
if (!fontStyle.isEmpty()) { if (!fontStyle.isEmpty()) {
if (fontStyle.contains("bold")) htmlBuilder.append("font-weight:bold;"); if (fontStyle.contains("bold")) htmlBuilder.append("font-weight:bold;");
} }
// Vertically center content
htmlBuilder.append("display:flex;align-items:center;");
htmlBuilder.append("'"); htmlBuilder.append("'");
htmlBuilder.append(">"); htmlBuilder.append(">");
@@ -308,7 +311,7 @@ public class CustomerInvoiceService {
.replace("'", "&#x27;"); .replace("'", "&#x27;");
if ("line".equals(type)) { if ("line".equals(type)) {
htmlBuilder.append("<hr style='margin:0;border:none;border-top:1px solid #333;height:0;'/>"); htmlBuilder.append("<hr style='margin:0;border:none;border-top:1px solid #333;height:0;width:100%;'/>");
} else if ("image".equals(type)) { } else if ("image".equals(type)) {
if (element.has("imageData")) { if (element.has("imageData")) {
String imageData = element.get("imageData").asText(); String imageData = element.get("imageData").asText();
@@ -317,7 +320,8 @@ public class CustomerInvoiceService {
htmlBuilder.append("[Bild]"); htmlBuilder.append("[Bild]");
} }
} else { } else {
htmlBuilder.append(text); // Wrap text in a span to prevent flexbox issues
htmlBuilder.append("<span style='white-space:nowrap;'>").append(text).append("</span>");
} }
htmlBuilder.append("</div>"); htmlBuilder.append("</div>");