Refactor signature handling and add PDF signature support
Refactored `OnSignatureButtonClick` in `EnvelopeViewer.razor`: - Converted to async and added null-check for `_capturedSignature`. - Integrated `pdfViewer.applySignature` to apply signatures to PDFs. Added `applySignature` method to `pdf-viewer.js`: - Handles rendering of signatures with image, metadata, and styling. - Follows German standards for signature formatting. - Includes error handling for missing elements. Introduced `escapeHtml` helper in `pdf-viewer.js` to prevent XSS. Updated `MaxThumbnailWidth` in `EnvelopeViewer.razor` to 400. Enhanced logging for better debugging during signature application.
This commit is contained in:
@@ -566,9 +566,19 @@ const int MaxThumbnailWidth = 400;
|
||||
}
|
||||
|
||||
[JSInvokable]
|
||||
public void OnSignatureButtonClick(int signatureId) {
|
||||
Console.WriteLine($"Signature #{signatureId} clicked");
|
||||
OpenSignaturePopup();
|
||||
public async Task OnSignatureButtonClick(int signatureId) {
|
||||
if (_capturedSignature == null) {
|
||||
// No signature captured yet - should not happen as popup is shown on page load
|
||||
return;
|
||||
}
|
||||
|
||||
// Apply signature to PDF canvas
|
||||
await JSRuntime.InvokeVoidAsync("pdfViewer.applySignature",
|
||||
signatureId,
|
||||
_capturedSignature.DataUrl,
|
||||
_capturedSignature.FullName,
|
||||
_capturedSignature.Position,
|
||||
_capturedSignature.Place);
|
||||
}
|
||||
|
||||
// Signature popup methods
|
||||
|
||||
@@ -594,7 +594,133 @@ window.pdfViewer = {
|
||||
}
|
||||
});
|
||||
this.signatureButtons = [];
|
||||
},
|
||||
|
||||
/**
|
||||
* Applies a signature to a specific signature field, removing the button and rendering the signature.
|
||||
* German standard: Signature image + Name, Position, Place, Date
|
||||
* @param {number} signatureId - ID of the signature field
|
||||
* @param {string} signatureDataUrl - Base64 PNG data URL of signature
|
||||
* @param {string} fullName - Signer's full name
|
||||
* @param {string} position - Signer's position (optional, can be empty)
|
||||
* @param {string} place - Signing place
|
||||
*/
|
||||
async applySignature(signatureId, signatureDataUrl, fullName, position, place) {
|
||||
try {
|
||||
// Find and remove the button
|
||||
const buttonIndex = this.signatureButtons.findIndex(btn => {
|
||||
return btn.getAttribute('data-signature-id') == signatureId;
|
||||
});
|
||||
|
||||
if (buttonIndex === -1) {
|
||||
console.warn(`Signature button #${signatureId} not found`);
|
||||
return;
|
||||
}
|
||||
|
||||
const button = this.signatureButtons[buttonIndex];
|
||||
const signatureLayer = document.getElementById('pdf-signature-layer');
|
||||
|
||||
if (!signatureLayer) {
|
||||
console.error('Signature layer not found');
|
||||
return;
|
||||
}
|
||||
|
||||
// Get button position before removing it
|
||||
const left = button.style.left;
|
||||
const top = button.style.top;
|
||||
|
||||
// Remove button
|
||||
if (button.parentNode) {
|
||||
button.parentNode.removeChild(button);
|
||||
}
|
||||
this.signatureButtons.splice(buttonIndex, 1);
|
||||
|
||||
// Create signature container
|
||||
const signatureContainer = document.createElement('div');
|
||||
signatureContainer.className = 'applied-signature';
|
||||
signatureContainer.setAttribute('data-signature-id', signatureId);
|
||||
signatureContainer.style.position = 'absolute';
|
||||
signatureContainer.style.left = left;
|
||||
signatureContainer.style.top = top;
|
||||
signatureContainer.style.width = '230px';
|
||||
signatureContainer.style.backgroundColor = '#f8f9fa';
|
||||
signatureContainer.style.border = '1px solid #dee2e6';
|
||||
signatureContainer.style.borderRadius = '6px';
|
||||
signatureContainer.style.padding = '12px';
|
||||
signatureContainer.style.boxShadow = '0 2px 8px rgba(0,0,0,0.1)';
|
||||
signatureContainer.style.fontFamily = "'Open Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif";
|
||||
|
||||
// Signature image
|
||||
const signatureImg = document.createElement('img');
|
||||
signatureImg.src = signatureDataUrl;
|
||||
signatureImg.alt = 'Unterschrift';
|
||||
signatureImg.style.width = '100%';
|
||||
signatureImg.style.height = 'auto';
|
||||
signatureImg.style.maxHeight = '70px';
|
||||
signatureImg.style.display = 'block';
|
||||
signatureImg.style.objectFit = 'contain';
|
||||
signatureImg.style.marginBottom = '6px';
|
||||
|
||||
// Separator line (German standard)
|
||||
const separator = document.createElement('div');
|
||||
separator.style.width = '100%';
|
||||
separator.style.height = '1px';
|
||||
separator.style.backgroundColor = '#495057';
|
||||
separator.style.marginBottom = '8px';
|
||||
|
||||
// Text information container
|
||||
const infoContainer = document.createElement('div');
|
||||
infoContainer.style.fontSize = '9px';
|
||||
infoContainer.style.lineHeight = '1.4';
|
||||
infoContainer.style.color = '#495057';
|
||||
infoContainer.style.fontWeight = '400';
|
||||
|
||||
// Format date (German style: dd.MM.yyyy)
|
||||
const today = new Date();
|
||||
const dateStr = today.toLocaleDateString('de-DE', {
|
||||
day: '2-digit',
|
||||
month: '2-digit',
|
||||
year: 'numeric'
|
||||
});
|
||||
|
||||
// Build text lines (German standard format)
|
||||
const lines = [];
|
||||
lines.push(`<strong style="font-weight: 600; color: #212529;">${this.escapeHtml(fullName)}</strong>`);
|
||||
|
||||
if (position && position.trim() !== '') {
|
||||
lines.push(`${this.escapeHtml(position)}`);
|
||||
}
|
||||
|
||||
lines.push(`${this.escapeHtml(place)}, ${dateStr}`);
|
||||
|
||||
infoContainer.innerHTML = lines.join('<br>');
|
||||
|
||||
// Assemble container
|
||||
signatureContainer.appendChild(signatureImg);
|
||||
signatureContainer.appendChild(separator);
|
||||
signatureContainer.appendChild(infoContainer);
|
||||
|
||||
// Add to signature layer
|
||||
signatureLayer.appendChild(signatureContainer);
|
||||
|
||||
console.log(`Signature #${signatureId} applied successfully`);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error applying signature:', error);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Escapes HTML to prevent XSS attacks
|
||||
*/
|
||||
escapeHtml(text) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user