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]
|
[JSInvokable]
|
||||||
public void OnSignatureButtonClick(int signatureId) {
|
public async Task OnSignatureButtonClick(int signatureId) {
|
||||||
Console.WriteLine($"Signature #{signatureId} clicked");
|
if (_capturedSignature == null) {
|
||||||
OpenSignaturePopup();
|
// 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
|
// Signature popup methods
|
||||||
|
|||||||
@@ -594,7 +594,133 @@ window.pdfViewer = {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.signatureButtons = [];
|
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