Erweiterungen
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
'use strict';
|
||||
|
||||
class InvoiceGenerator {
|
||||
constructor() {
|
||||
constructor(containerId) {
|
||||
this.canvas = null;
|
||||
this.ctx = null;
|
||||
this.elements = new Map();
|
||||
@@ -16,22 +16,33 @@
|
||||
this.elementStart = { x: 0, y: 0, w: 0, h: 0 };
|
||||
this.activeHandle = null;
|
||||
this.container = null;
|
||||
this.containerId = containerId || 'invoice-canvas-container';
|
||||
|
||||
// Page dimensions (A4 at 96 DPI)
|
||||
this.pageX = 50;
|
||||
this.pageY = 30;
|
||||
this.pageWidth = 794;
|
||||
this.pageHeight = 1123;
|
||||
this.scale = 1;
|
||||
|
||||
// Handle configuration
|
||||
this.handleSize = 10;
|
||||
this.handlePadding = 3;
|
||||
|
||||
// Grid configuration (Snap to Grid)
|
||||
this.gridSize = 5;
|
||||
this.showGrid = true;
|
||||
}
|
||||
|
||||
// Snap value to grid
|
||||
snapToGrid(value) {
|
||||
return Math.round(value / this.gridSize) * this.gridSize;
|
||||
}
|
||||
|
||||
init() {
|
||||
this.container = document.getElementById('invoice-canvas-container');
|
||||
this.container = document.getElementById(this.containerId);
|
||||
if (!this.container) {
|
||||
console.error('Canvas container not found');
|
||||
console.error('Canvas container not found: ' + this.containerId);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -50,6 +61,10 @@
|
||||
this.canvas.addEventListener('mouseup', this.onMouseUp.bind(this));
|
||||
this.canvas.addEventListener('mouseleave', this.onMouseUp.bind(this));
|
||||
|
||||
// Keyboard navigation for selected element
|
||||
this.canvas.setAttribute('tabindex', '0');
|
||||
this.canvas.addEventListener('keydown', this.onKeyDown.bind(this));
|
||||
|
||||
window.addEventListener('resize', () => {
|
||||
this.updateCanvasSize();
|
||||
this.draw();
|
||||
@@ -63,14 +78,32 @@
|
||||
const rect = this.container.getBoundingClientRect();
|
||||
this.canvas.width = rect.width;
|
||||
this.canvas.height = rect.height;
|
||||
this.pageX = Math.max(20, (this.canvas.width - this.pageWidth) / 2);
|
||||
|
||||
// Calculate scale to maximize page size while maintaining aspect ratio
|
||||
// Use full available space with minimal padding
|
||||
const padding = 10;
|
||||
const availableWidth = this.canvas.width - padding * 2;
|
||||
const availableHeight = this.canvas.height - padding * 2;
|
||||
|
||||
// Scale to fit the available space (can be larger than 1 if container is bigger than A4)
|
||||
this.scale = Math.min(availableWidth / this.pageWidth, availableHeight / this.pageHeight);
|
||||
|
||||
// Center the scaled page
|
||||
const scaledPageWidth = this.pageWidth * this.scale;
|
||||
const scaledPageHeight = this.pageHeight * this.scale;
|
||||
this.pageX = (this.canvas.width - scaledPageWidth) / 2;
|
||||
this.pageY = (this.canvas.height - scaledPageHeight) / 2;
|
||||
}
|
||||
|
||||
getMousePos(e) {
|
||||
const rect = this.canvas.getBoundingClientRect();
|
||||
// Convert screen coordinates to canvas coordinates
|
||||
const canvasX = e.clientX - rect.left;
|
||||
const canvasY = e.clientY - rect.top;
|
||||
// Convert to page coordinates (accounting for scale and offset)
|
||||
return {
|
||||
x: e.clientX - rect.left,
|
||||
y: e.clientY - rect.top
|
||||
x: (canvasX - this.pageX) / this.scale,
|
||||
y: (canvasY - this.pageY) / this.scale
|
||||
};
|
||||
}
|
||||
|
||||
@@ -166,14 +199,18 @@
|
||||
const dx = pos.x - this.dragStart.x;
|
||||
const dy = pos.y - this.dragStart.y;
|
||||
|
||||
this.selectedElement.x = this.elementStart.x + dx;
|
||||
this.selectedElement.y = this.elementStart.y + dy;
|
||||
let newX = this.elementStart.x + dx;
|
||||
let newY = this.elementStart.y + dy;
|
||||
|
||||
// Constrain to page
|
||||
this.selectedElement.x = Math.max(this.pageX, Math.min(this.selectedElement.x,
|
||||
this.pageX + this.pageWidth - (this.selectedElement.width || 100)));
|
||||
this.selectedElement.y = Math.max(this.pageY, Math.min(this.selectedElement.y,
|
||||
this.pageY + this.pageHeight - (this.selectedElement.height || 30)));
|
||||
// Constrain to page (in page coordinates, page starts at 0,0)
|
||||
newX = Math.max(0, Math.min(newX,
|
||||
this.pageWidth - (this.selectedElement.width || 100)));
|
||||
newY = Math.max(0, Math.min(newY,
|
||||
this.pageHeight - (this.selectedElement.height || 30)));
|
||||
|
||||
// Snap to grid
|
||||
this.selectedElement.x = this.snapToGrid(newX);
|
||||
this.selectedElement.y = this.snapToGrid(newY);
|
||||
|
||||
this.draw();
|
||||
this.notifyChange();
|
||||
@@ -211,6 +248,47 @@
|
||||
this.canvas.style.cursor = 'default';
|
||||
}
|
||||
|
||||
onKeyDown(e) {
|
||||
if (!this.selectedElement) return;
|
||||
|
||||
const step = this.gridSize; // Move by grid size (5px)
|
||||
let moved = false;
|
||||
|
||||
switch(e.key) {
|
||||
case 'ArrowUp':
|
||||
this.selectedElement.y = Math.max(0, this.selectedElement.y - step);
|
||||
moved = true;
|
||||
e.preventDefault();
|
||||
break;
|
||||
case 'ArrowDown':
|
||||
this.selectedElement.y = Math.min(
|
||||
this.pageHeight - (this.selectedElement.height || 30),
|
||||
this.selectedElement.y + step
|
||||
);
|
||||
moved = true;
|
||||
e.preventDefault();
|
||||
break;
|
||||
case 'ArrowLeft':
|
||||
this.selectedElement.x = Math.max(0, this.selectedElement.x - step);
|
||||
moved = true;
|
||||
e.preventDefault();
|
||||
break;
|
||||
case 'ArrowRight':
|
||||
this.selectedElement.x = Math.min(
|
||||
this.pageWidth - (this.selectedElement.width || 100),
|
||||
this.selectedElement.x + step
|
||||
);
|
||||
moved = true;
|
||||
e.preventDefault();
|
||||
break;
|
||||
}
|
||||
|
||||
if (moved) {
|
||||
this.draw();
|
||||
this.notifyChange();
|
||||
}
|
||||
}
|
||||
|
||||
doResize(mouseX, mouseY) {
|
||||
if (!this.selectedElement || !this.activeHandle) return;
|
||||
|
||||
@@ -308,17 +386,31 @@
|
||||
ctx.fillStyle = '#e8e8e8';
|
||||
ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
|
||||
|
||||
// Page shadow
|
||||
ctx.save();
|
||||
|
||||
// Apply scale and offset
|
||||
ctx.translate(this.pageX, this.pageY);
|
||||
ctx.scale(this.scale, this.scale);
|
||||
|
||||
// Page shadow (drawn before scaling offset)
|
||||
ctx.save();
|
||||
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
||||
ctx.fillStyle = 'rgba(0,0,0,0.2)';
|
||||
ctx.fillRect(this.pageX + 4, this.pageY + this.pageHeight, this.pageWidth, 4);
|
||||
ctx.fillRect(this.pageX + this.pageWidth, this.pageY + 4, 4, this.pageHeight);
|
||||
ctx.fillRect(this.pageX + 4 * this.scale, this.pageY + this.pageHeight * this.scale, this.pageWidth * this.scale, 4);
|
||||
ctx.fillRect(this.pageX + this.pageWidth * this.scale, this.pageY + 4 * this.scale, 4, this.pageHeight * this.scale);
|
||||
ctx.restore();
|
||||
|
||||
// Page
|
||||
ctx.fillStyle = '#ffffff';
|
||||
ctx.fillRect(this.pageX, this.pageY, this.pageWidth, this.pageHeight);
|
||||
ctx.fillRect(0, 0, this.pageWidth, this.pageHeight);
|
||||
ctx.strokeStyle = '#cccccc';
|
||||
ctx.lineWidth = 1;
|
||||
ctx.strokeRect(this.pageX, this.pageY, this.pageWidth, this.pageHeight);
|
||||
ctx.lineWidth = 1 / this.scale;
|
||||
ctx.strokeRect(0, 0, this.pageWidth, this.pageHeight);
|
||||
|
||||
// Draw grid if enabled
|
||||
if (this.showGrid) {
|
||||
this.drawGrid(ctx);
|
||||
}
|
||||
|
||||
// Draw elements
|
||||
this.elements.forEach(el => this.drawElement(el));
|
||||
@@ -327,6 +419,8 @@
|
||||
if (this.selectedElement) {
|
||||
this.drawSelection(this.selectedElement);
|
||||
}
|
||||
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
drawImagePlaceholder(ctx, el) {
|
||||
@@ -491,7 +585,7 @@
|
||||
const y = el.y;
|
||||
const w = el.width || 150;
|
||||
const h = el.height || 30;
|
||||
const hs = this.handleSize;
|
||||
const hs = this.handleSize / this.scale;
|
||||
|
||||
// Selection border
|
||||
ctx.strokeStyle = '#1976d2';
|
||||
@@ -522,13 +616,43 @@
|
||||
});
|
||||
}
|
||||
|
||||
drawGrid(ctx) {
|
||||
ctx.save();
|
||||
ctx.strokeStyle = 'rgba(200, 200, 200, 0.3)';
|
||||
ctx.lineWidth = 0.5 / this.scale;
|
||||
|
||||
// Vertical lines (draw in page coordinates)
|
||||
for (let x = 0; x <= this.pageWidth; x += this.gridSize) {
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x, 0);
|
||||
ctx.lineTo(x, this.pageHeight);
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
// Horizontal lines
|
||||
for (let y = 0; y <= this.pageHeight; y += this.gridSize) {
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(0, y);
|
||||
ctx.lineTo(this.pageWidth, y);
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
addElement(type, label, dropX, dropY) {
|
||||
this.elementCounter++;
|
||||
const id = `element-${this.elementCounter}`;
|
||||
|
||||
const rect = this.container.getBoundingClientRect();
|
||||
const x = Math.max(this.pageX + 10, dropX - rect.left - 50);
|
||||
const y = Math.max(this.pageY + 10, dropY - rect.top - 15);
|
||||
// dropX and dropY are in container coordinates, convert to page coordinates
|
||||
const pageCoordX = (dropX - this.pageX) / this.scale;
|
||||
const pageCoordY = (dropY - this.pageY) / this.scale;
|
||||
|
||||
// Snap initial position to grid (relative to page origin)
|
||||
const rawX = Math.max(10, pageCoordX - 50);
|
||||
const rawY = Math.max(10, pageCoordY - 15);
|
||||
const x = this.snapToGrid(rawX);
|
||||
const y = this.snapToGrid(rawY);
|
||||
|
||||
let el = {
|
||||
id, type, x, y,
|
||||
@@ -595,6 +719,10 @@
|
||||
this.draw();
|
||||
if (this.selectedElement) {
|
||||
this.notifyChange();
|
||||
// Focus canvas for keyboard navigation
|
||||
if (this.canvas) {
|
||||
this.canvas.focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -617,8 +745,8 @@
|
||||
updateElementPosition(id, x, y) {
|
||||
const el = this.elements.get(id);
|
||||
if (el) {
|
||||
if (x !== null) el.x = x;
|
||||
if (y !== null) el.y = y;
|
||||
if (x !== null) el.x = this.snapToGrid(x);
|
||||
if (y !== null) el.y = this.snapToGrid(y);
|
||||
this.draw();
|
||||
}
|
||||
}
|
||||
@@ -631,6 +759,14 @@
|
||||
}
|
||||
}
|
||||
|
||||
updateElementColor(id, color) {
|
||||
const el = this.elements.get(id);
|
||||
if (el) {
|
||||
el.color = color;
|
||||
this.draw();
|
||||
}
|
||||
}
|
||||
|
||||
updateElementImage(id, imageDataUrl) {
|
||||
const el = this.elements.get(id);
|
||||
if (el && el.type === 'image') {
|
||||
@@ -683,6 +819,12 @@
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
exportTemplateJson() {
|
||||
// Return template data as JSON string for Java processing
|
||||
const data = this.getCanvasData();
|
||||
return JSON.stringify(data);
|
||||
}
|
||||
|
||||
generatePreview() {
|
||||
const temp = document.createElement('canvas');
|
||||
temp.width = this.pageWidth;
|
||||
@@ -718,4 +860,5 @@
|
||||
}
|
||||
|
||||
window.invoiceGenerator = new InvoiceGenerator();
|
||||
window.InvoiceGenerator = InvoiceGenerator;
|
||||
})();
|
||||
|
||||
534
src/main/frontend/invoice-generator/profile-invoice-generator.js
Normal file
534
src/main/frontend/invoice-generator/profile-invoice-generator.js
Normal file
@@ -0,0 +1,534 @@
|
||||
// Profile Invoice Generator - Initializes canvas on the edit-profile page
|
||||
window.initProfileInvoiceGenerator = function() {
|
||||
var containerId = 'invoice-canvas-container-profile';
|
||||
var container = document.getElementById(containerId);
|
||||
|
||||
if (!container) {
|
||||
console.error('Canvas container not found:', containerId);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if canvas already exists
|
||||
var existingCanvas = container.querySelector('canvas');
|
||||
if (existingCanvas) {
|
||||
console.log('Canvas already exists');
|
||||
return;
|
||||
}
|
||||
|
||||
// Get container dimensions
|
||||
var rect = container.getBoundingClientRect();
|
||||
if (rect.width === 0 || rect.height === 0) {
|
||||
console.error('Container has no size');
|
||||
return;
|
||||
}
|
||||
|
||||
// Create canvas element
|
||||
var canvas = document.createElement('canvas');
|
||||
canvas.style.display = 'block';
|
||||
canvas.width = rect.width;
|
||||
canvas.height = rect.height;
|
||||
container.appendChild(canvas);
|
||||
|
||||
var ctx = canvas.getContext('2d');
|
||||
|
||||
// State
|
||||
var elements = [];
|
||||
var selectedElement = null;
|
||||
var isDragging = false;
|
||||
var dragStart = { x: 0, y: 0 };
|
||||
var elementStart = { x: 0, y: 0 };
|
||||
var elementCounter = 0;
|
||||
var gridSize = 5;
|
||||
|
||||
// Page dimensions
|
||||
var padding = 10;
|
||||
var pageX, pageY, pageWidth, pageHeight;
|
||||
|
||||
function updatePageDimensions() {
|
||||
var w = canvas.width;
|
||||
var h = canvas.height;
|
||||
var availableWidth = w - padding * 2;
|
||||
var availableHeight = h - padding * 2;
|
||||
pageWidth = Math.min(availableWidth, availableHeight * 0.707);
|
||||
pageHeight = pageWidth / 0.707;
|
||||
pageX = (w - pageWidth) / 2;
|
||||
pageY = (h - pageHeight) / 2;
|
||||
}
|
||||
|
||||
function snapToGrid(value) {
|
||||
return Math.round(value / gridSize) * gridSize;
|
||||
}
|
||||
|
||||
// Notify Java about element selection
|
||||
function notifyElementSelected(el) {
|
||||
if (window.invoiceGeneratorViewProfile && window.invoiceGeneratorViewProfile.$server) {
|
||||
window.invoiceGeneratorViewProfile.$server.updatePropertiesPanel(
|
||||
el.id,
|
||||
el.type,
|
||||
el.text || '',
|
||||
el.x,
|
||||
el.y,
|
||||
el.fontSize || 14,
|
||||
el.color || '#333333',
|
||||
el.width || 100,
|
||||
el.height || 30
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function notifyElementDeselected() {
|
||||
if (window.invoiceGeneratorViewProfile && window.invoiceGeneratorViewProfile.$server) {
|
||||
window.invoiceGeneratorViewProfile.$server.resetPropertiesPanel();
|
||||
}
|
||||
}
|
||||
|
||||
// Draw function
|
||||
function draw() {
|
||||
var w = canvas.width;
|
||||
var h = canvas.height;
|
||||
|
||||
// Clear background
|
||||
ctx.fillStyle = '#e8e8e8';
|
||||
ctx.fillRect(0, 0, w, h);
|
||||
|
||||
updatePageDimensions();
|
||||
|
||||
// Page shadow
|
||||
ctx.fillStyle = 'rgba(0,0,0,0.2)';
|
||||
ctx.fillRect(pageX + 4, pageY + pageHeight, pageWidth, 4);
|
||||
ctx.fillRect(pageX + pageWidth, pageY + 4, 4, pageHeight);
|
||||
|
||||
// White page
|
||||
ctx.fillStyle = '#ffffff';
|
||||
ctx.fillRect(pageX, pageY, pageWidth, pageHeight);
|
||||
ctx.strokeStyle = '#cccccc';
|
||||
ctx.lineWidth = 1;
|
||||
ctx.strokeRect(pageX, pageY, pageWidth, pageHeight);
|
||||
|
||||
// Grid
|
||||
ctx.strokeStyle = 'rgba(200, 200, 200, 0.3)';
|
||||
ctx.lineWidth = 0.5;
|
||||
for (var x = pageX; x <= pageX + pageWidth; x += gridSize) {
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x, pageY);
|
||||
ctx.lineTo(x, pageY + pageHeight);
|
||||
ctx.stroke();
|
||||
}
|
||||
for (var y = pageY; y <= pageY + pageHeight; y += gridSize) {
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(pageX, y);
|
||||
ctx.lineTo(pageX + pageWidth, y);
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
// Draw elements
|
||||
elements.forEach(function(el) {
|
||||
drawElement(el);
|
||||
});
|
||||
|
||||
// Draw selection
|
||||
if (selectedElement) {
|
||||
drawSelection(selectedElement);
|
||||
}
|
||||
}
|
||||
|
||||
function drawElement(el) {
|
||||
ctx.save();
|
||||
|
||||
if (el.type === 'line') {
|
||||
ctx.strokeStyle = el.color || '#333333';
|
||||
ctx.lineWidth = 1;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(pageX + el.x, pageY + el.y);
|
||||
ctx.lineTo(pageX + el.x + el.width, pageY + el.y);
|
||||
ctx.stroke();
|
||||
} else if (el.type === 'image') {
|
||||
ctx.fillStyle = '#f0f0f0';
|
||||
ctx.fillRect(pageX + el.x, pageY + el.y, el.width, el.height);
|
||||
ctx.strokeStyle = '#999999';
|
||||
ctx.strokeRect(pageX + el.x, pageY + el.y, el.width, el.height);
|
||||
ctx.fillStyle = '#666666';
|
||||
ctx.font = '12px Arial';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.fillText('Bild', pageX + el.x + el.width / 2, pageY + el.y + el.height / 2);
|
||||
} else {
|
||||
// Text elements
|
||||
ctx.font = (el.fontStyle || '') + ' ' + (el.fontSize || 14) + 'px Arial';
|
||||
ctx.fillStyle = el.color || '#333333';
|
||||
ctx.textBaseline = 'top';
|
||||
|
||||
var lines = (el.text || '').split('\n');
|
||||
var lineHeight = (el.fontSize || 14) * 1.2;
|
||||
var y = pageY + el.y;
|
||||
|
||||
lines.forEach(function(line) {
|
||||
ctx.fillText(line, pageX + el.x, y);
|
||||
y += lineHeight;
|
||||
});
|
||||
}
|
||||
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
function drawSelection(el) {
|
||||
var x = pageX + el.x;
|
||||
var y = pageY + el.y;
|
||||
var w = el.width || 100;
|
||||
var h = el.height || 30;
|
||||
var hs = 8; // handle size
|
||||
|
||||
ctx.strokeStyle = '#1976d2';
|
||||
ctx.lineWidth = 2;
|
||||
ctx.setLineDash([5, 3]);
|
||||
ctx.strokeRect(x, y, w, h);
|
||||
ctx.setLineDash([]);
|
||||
|
||||
// Handles
|
||||
ctx.fillStyle = '#ffffff';
|
||||
ctx.strokeStyle = '#1976d2';
|
||||
ctx.lineWidth = 2;
|
||||
|
||||
var positions = [
|
||||
[x - hs/2, y - hs/2],
|
||||
[x + w/2 - hs/2, y - hs/2],
|
||||
[x + w - hs/2, y - hs/2],
|
||||
[x - hs/2, y + h/2 - hs/2],
|
||||
[x + w - hs/2, y + h/2 - hs/2],
|
||||
[x - hs/2, y + h - hs/2],
|
||||
[x + w/2 - hs/2, y + h - hs/2],
|
||||
[x + w - hs/2, y + h - hs/2]
|
||||
];
|
||||
|
||||
positions.forEach(function(pos) {
|
||||
ctx.fillRect(pos[0], pos[1], hs, hs);
|
||||
ctx.strokeRect(pos[0], pos[1], hs, hs);
|
||||
});
|
||||
}
|
||||
|
||||
function hitTest(x, y, el) {
|
||||
var ex = pageX + el.x;
|
||||
var ey = pageY + el.y;
|
||||
var ew = el.width || 100;
|
||||
var eh = el.height || 30;
|
||||
return x >= ex && x <= ex + ew && y >= ey && y <= ey + eh;
|
||||
}
|
||||
|
||||
// Mouse events
|
||||
canvas.addEventListener('mousedown', function(e) {
|
||||
var rect = canvas.getBoundingClientRect();
|
||||
var x = e.clientX - rect.left;
|
||||
var y = e.clientY - rect.top;
|
||||
|
||||
// Check if clicking on an element
|
||||
var clickedElement = null;
|
||||
for (var i = elements.length - 1; i >= 0; i--) {
|
||||
if (hitTest(x, y, elements[i])) {
|
||||
clickedElement = elements[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (clickedElement) {
|
||||
selectedElement = clickedElement;
|
||||
isDragging = true;
|
||||
dragStart = { x: x, y: y };
|
||||
elementStart = { x: clickedElement.x, y: clickedElement.y };
|
||||
canvas.style.cursor = 'move';
|
||||
notifyElementSelected(selectedElement);
|
||||
} else {
|
||||
selectedElement = null;
|
||||
notifyElementDeselected();
|
||||
}
|
||||
|
||||
draw();
|
||||
});
|
||||
|
||||
canvas.addEventListener('mousemove', function(e) {
|
||||
var rect = canvas.getBoundingClientRect();
|
||||
var x = e.clientX - rect.left;
|
||||
var y = e.clientY - rect.top;
|
||||
|
||||
if (isDragging && selectedElement) {
|
||||
var dx = x - dragStart.x;
|
||||
var dy = y - dragStart.y;
|
||||
|
||||
var newX = elementStart.x + dx;
|
||||
var newY = elementStart.y + dy;
|
||||
|
||||
// Constrain to page
|
||||
newX = Math.max(0, Math.min(newX, pageWidth - (selectedElement.width || 100)));
|
||||
newY = Math.max(0, Math.min(newY, pageHeight - (selectedElement.height || 30)));
|
||||
|
||||
// Snap to grid
|
||||
selectedElement.x = snapToGrid(newX);
|
||||
selectedElement.y = snapToGrid(newY);
|
||||
|
||||
draw();
|
||||
notifyElementSelected(selectedElement);
|
||||
} else {
|
||||
// Update cursor
|
||||
var hovering = false;
|
||||
for (var i = elements.length - 1; i >= 0; i--) {
|
||||
if (hitTest(x, y, elements[i])) {
|
||||
hovering = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
canvas.style.cursor = hovering ? 'move' : 'default';
|
||||
}
|
||||
});
|
||||
|
||||
canvas.addEventListener('mouseup', function() {
|
||||
isDragging = false;
|
||||
canvas.style.cursor = 'default';
|
||||
});
|
||||
|
||||
canvas.addEventListener('mouseleave', function() {
|
||||
isDragging = false;
|
||||
canvas.style.cursor = 'default';
|
||||
});
|
||||
|
||||
// Keyboard navigation
|
||||
canvas.setAttribute('tabindex', '0');
|
||||
canvas.addEventListener('keydown', function(e) {
|
||||
if (!selectedElement) return;
|
||||
|
||||
var step = gridSize;
|
||||
var moved = false;
|
||||
|
||||
switch(e.key) {
|
||||
case 'ArrowUp':
|
||||
selectedElement.y = Math.max(0, selectedElement.y - step);
|
||||
moved = true;
|
||||
e.preventDefault();
|
||||
break;
|
||||
case 'ArrowDown':
|
||||
selectedElement.y = Math.min(pageHeight - (selectedElement.height || 30), selectedElement.y + step);
|
||||
moved = true;
|
||||
e.preventDefault();
|
||||
break;
|
||||
case 'ArrowLeft':
|
||||
selectedElement.x = Math.max(0, selectedElement.x - step);
|
||||
moved = true;
|
||||
e.preventDefault();
|
||||
break;
|
||||
case 'ArrowRight':
|
||||
selectedElement.x = Math.min(pageWidth - (selectedElement.width || 100), selectedElement.x + step);
|
||||
moved = true;
|
||||
e.preventDefault();
|
||||
break;
|
||||
case 'Delete':
|
||||
var index = elements.indexOf(selectedElement);
|
||||
if (index > -1) {
|
||||
elements.splice(index, 1);
|
||||
selectedElement = null;
|
||||
notifyElementDeselected();
|
||||
draw();
|
||||
}
|
||||
e.preventDefault();
|
||||
break;
|
||||
}
|
||||
|
||||
if (moved) {
|
||||
draw();
|
||||
notifyElementSelected(selectedElement);
|
||||
}
|
||||
});
|
||||
|
||||
// Add element function
|
||||
window.addProfileElement = function(type, label, dropX, dropY) {
|
||||
elementCounter++;
|
||||
var id = 'element-' + elementCounter;
|
||||
|
||||
// Convert to page coordinates
|
||||
var x = dropX - pageX - 50;
|
||||
var y = dropY - pageY - 15;
|
||||
x = Math.max(10, x);
|
||||
y = Math.max(10, y);
|
||||
x = snapToGrid(x);
|
||||
y = snapToGrid(y);
|
||||
|
||||
var el = {
|
||||
id: id,
|
||||
type: type,
|
||||
x: x,
|
||||
y: y,
|
||||
width: 150,
|
||||
height: 30,
|
||||
fontSize: 14,
|
||||
color: '#333333'
|
||||
};
|
||||
|
||||
switch (type) {
|
||||
case 'text':
|
||||
el.text = 'Text eingeben...';
|
||||
el.height = 20;
|
||||
break;
|
||||
case 'header':
|
||||
el.text = 'Überschrift';
|
||||
el.fontSize = 24;
|
||||
el.fontStyle = 'bold';
|
||||
el.color = '#000000';
|
||||
el.width = 200;
|
||||
el.height = 30;
|
||||
break;
|
||||
case 'date':
|
||||
el.text = 'Datum: ' + new Date().toLocaleDateString('de-DE');
|
||||
el.fontSize = 12;
|
||||
el.color = '#666666';
|
||||
el.height = 16;
|
||||
break;
|
||||
case 'customer':
|
||||
el.text = 'Kundenname\nStraße Nr.\nPLZ Ort';
|
||||
el.height = 50;
|
||||
el.fontSize = 12;
|
||||
break;
|
||||
case 'company':
|
||||
el.text = 'Ihr Unternehmen\nIhre Straße\nIhre PLZ Ort';
|
||||
el.height = 50;
|
||||
el.fontSize = 12;
|
||||
break;
|
||||
case 'amount':
|
||||
el.text = 'Gesamtbetrag: 0,00 €';
|
||||
el.fontStyle = 'bold';
|
||||
el.width = 180;
|
||||
el.height = 20;
|
||||
break;
|
||||
case 'line':
|
||||
el.text = '';
|
||||
el.width = 200;
|
||||
el.height = 2;
|
||||
break;
|
||||
case 'image':
|
||||
el.text = 'Bild';
|
||||
el.width = 100;
|
||||
el.height = 100;
|
||||
break;
|
||||
default:
|
||||
el.text = label || 'Neues Element';
|
||||
el.height = 20;
|
||||
}
|
||||
|
||||
elements.push(el);
|
||||
selectedElement = el;
|
||||
draw();
|
||||
notifyElementSelected(el);
|
||||
|
||||
// Focus canvas for keyboard navigation
|
||||
canvas.focus();
|
||||
};
|
||||
|
||||
// Update element functions
|
||||
window.updateProfileElementText = function(id, text) {
|
||||
var el = elements.find(function(e) { return e.id === id; });
|
||||
if (el) {
|
||||
el.text = text;
|
||||
draw();
|
||||
}
|
||||
};
|
||||
|
||||
window.updateProfileElementPosition = function(id, x, y) {
|
||||
var el = elements.find(function(e) { return e.id === id; });
|
||||
if (el) {
|
||||
if (x !== null) el.x = snapToGrid(x);
|
||||
if (y !== null) el.y = snapToGrid(y);
|
||||
draw();
|
||||
}
|
||||
};
|
||||
|
||||
window.updateProfileElementFontSize = function(id, size) {
|
||||
var el = elements.find(function(e) { return e.id === id; });
|
||||
if (el) {
|
||||
el.fontSize = size;
|
||||
draw();
|
||||
}
|
||||
};
|
||||
|
||||
window.updateProfileElementColor = function(id, color) {
|
||||
var el = elements.find(function(e) { return e.id === id; });
|
||||
if (el) {
|
||||
el.color = color;
|
||||
draw();
|
||||
}
|
||||
};
|
||||
|
||||
window.updateProfileElementSize = function(id, width, height) {
|
||||
var el = elements.find(function(e) { return e.id === id; });
|
||||
if (el) {
|
||||
if (width !== null) el.width = width;
|
||||
if (height !== null) el.height = height;
|
||||
draw();
|
||||
}
|
||||
};
|
||||
|
||||
window.deleteProfileElement = function(id) {
|
||||
var index = elements.findIndex(function(e) { return e.id === id; });
|
||||
if (index > -1) {
|
||||
elements.splice(index, 1);
|
||||
if (selectedElement && selectedElement.id === id) {
|
||||
selectedElement = null;
|
||||
notifyElementDeselected();
|
||||
}
|
||||
draw();
|
||||
}
|
||||
};
|
||||
|
||||
// Clear canvas function
|
||||
window.clearProfileCanvas = function() {
|
||||
elements = [];
|
||||
selectedElement = null;
|
||||
notifyElementDeselected();
|
||||
draw();
|
||||
};
|
||||
|
||||
// Get canvas data
|
||||
window.getProfileCanvasData = function() {
|
||||
return {
|
||||
elements: elements
|
||||
};
|
||||
};
|
||||
|
||||
draw();
|
||||
console.log('Profile canvas initialized');
|
||||
|
||||
// Handle window resize
|
||||
window.addEventListener('resize', function() {
|
||||
var newRect = container.getBoundingClientRect();
|
||||
if (newRect.width > 0 && newRect.height > 0) {
|
||||
canvas.width = newRect.width;
|
||||
canvas.height = newRect.height;
|
||||
draw();
|
||||
}
|
||||
});
|
||||
|
||||
// Setup drop zone
|
||||
if (!container._dropSetup) {
|
||||
container._dropSetup = true;
|
||||
|
||||
container.addEventListener('dragover', function(e) {
|
||||
e.preventDefault();
|
||||
e.dataTransfer.dropEffect = 'copy';
|
||||
container.style.borderColor = 'var(--lumo-primary-color)';
|
||||
});
|
||||
|
||||
container.addEventListener('dragleave', function(e) {
|
||||
container.style.borderColor = 'var(--lumo-contrast-20pct)';
|
||||
});
|
||||
|
||||
container.addEventListener('drop', function(e) {
|
||||
e.preventDefault();
|
||||
container.style.borderColor = 'var(--lumo-contrast-20pct)';
|
||||
|
||||
var templateType = e.dataTransfer.getData('template-type');
|
||||
var templateLabel = e.dataTransfer.getData('template-label');
|
||||
|
||||
if (templateType && window.addProfileElement) {
|
||||
var rect = container.getBoundingClientRect();
|
||||
var x = e.clientX - rect.left;
|
||||
var y = e.clientY - rect.top;
|
||||
window.addProfileElement(templateType, templateLabel, x, y);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user