Add interactive signature buttons to PDF viewer
Introduced functionality to render interactive signature buttons on the PDF viewer. Added support for fetching and displaying signature data (`SignatureDto`) dynamically based on the current page. - Added `@using` directives in `EnvelopeViewer.razor` for required namespaces. - Introduced `_signatures` field to store signature data. - Updated `OnInitializedAsync` to fetch and process signatures. - Implemented `RenderSignatureButtonsAsync` to dynamically render buttons. - Added `[JSInvokable]` method `OnSignatureButtonClick` for button events. - Updated CSS to style `pdf-signature-layer` and `signature-button`. - Enhanced `pdf-viewer.js` with methods to render and clear buttons. - Ensured buttons respond to zoom and page navigation changes. - Added error handling and logging for signature rendering. These changes improve user interaction by enabling signing functionality directly on the PDF viewer.
This commit is contained in:
@@ -451,6 +451,150 @@ window.pdfViewer = {
|
||||
|
||||
startResize() {
|
||||
this.isResizing = true;
|
||||
},
|
||||
|
||||
// Signature button functionality
|
||||
signatureButtons: [],
|
||||
|
||||
/**
|
||||
* Renders clickable signature buttons on the PDF canvas.
|
||||
* @param {Array} signatures - Array of SignatureDto objects with x, y coordinates in PDF POINTS
|
||||
* @param {number} currentPageNum - Current page number (1-based)
|
||||
* @param {object} dotNetRef - .NET reference for callbacks
|
||||
*/
|
||||
async renderSignatureButtons(signatures, currentPageNum, dotNetRef) {
|
||||
// Clear existing buttons
|
||||
this.clearSignatureButtons();
|
||||
|
||||
if (!this.pdfDoc || !signatures || signatures.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.dotNetReference = dotNetRef;
|
||||
|
||||
try {
|
||||
// Filter signatures for current page
|
||||
const pageSignatures = signatures.filter(sig => sig.page === currentPageNum);
|
||||
|
||||
if (pageSignatures.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get current page and viewport
|
||||
const page = await this.pdfDoc.getPage(currentPageNum);
|
||||
const dpr = this.qualityOptions.mainCanvasEnableHiDPI
|
||||
? Math.min(window.devicePixelRatio || 1, this.qualityOptions.mainCanvasMaxDPR)
|
||||
: 1.0;
|
||||
const viewport = page.getViewport({ scale: this.scale * dpr });
|
||||
|
||||
// Get signature layer container
|
||||
const signatureLayer = document.getElementById('pdf-signature-layer');
|
||||
if (!signatureLayer) {
|
||||
console.warn('Signature layer not found');
|
||||
return;
|
||||
}
|
||||
|
||||
// Set signature layer dimensions to match canvas display size
|
||||
signatureLayer.style.width = `${viewport.width / dpr}px`;
|
||||
signatureLayer.style.height = `${viewport.height / dpr}px`;
|
||||
|
||||
// Create button for each signature
|
||||
pageSignatures.forEach(sig => {
|
||||
// Coordinates are in PDF POINTS - convert to display pixels
|
||||
const xPx = (sig.x * this.scale);
|
||||
const yPx = (sig.y * this.scale);
|
||||
|
||||
// Create button element
|
||||
const button = document.createElement('button');
|
||||
button.className = 'signature-button';
|
||||
button.setAttribute('data-signature-id', sig.id);
|
||||
button.setAttribute('type', 'button');
|
||||
button.setAttribute('tabindex', '0');
|
||||
button.style.position = 'absolute';
|
||||
button.style.left = `${xPx}px`;
|
||||
button.style.top = `${yPx}px`;
|
||||
button.style.width = '150px';
|
||||
button.style.height = '60px';
|
||||
button.style.backgroundColor = '#4F46E5';
|
||||
button.style.color = 'white';
|
||||
button.style.border = 'none';
|
||||
button.style.borderRadius = '8px';
|
||||
button.style.cursor = 'pointer';
|
||||
button.style.fontSize = '16px';
|
||||
button.style.fontWeight = '600';
|
||||
button.style.display = 'flex';
|
||||
button.style.flexDirection = 'column';
|
||||
button.style.alignItems = 'center';
|
||||
button.style.justifyContent = 'center';
|
||||
button.style.gap = '4px';
|
||||
button.style.boxShadow = '0 2px 8px rgba(0,0,0,0.15)';
|
||||
button.style.transition = 'all 0.2s ease';
|
||||
button.style.zIndex = '100';
|
||||
|
||||
// Add text
|
||||
const textDiv = document.createElement('div');
|
||||
textDiv.textContent = 'Sign';
|
||||
textDiv.style.fontSize = '18px';
|
||||
textDiv.style.fontWeight = '700';
|
||||
|
||||
// Add SVG icon
|
||||
const svgNS = 'http://www.w3.org/2000/svg';
|
||||
const svg = document.createElementNS(svgNS, 'svg');
|
||||
svg.setAttribute('width', '24');
|
||||
svg.setAttribute('height', '24');
|
||||
svg.setAttribute('viewBox', '0 8 32 36');
|
||||
svg.setAttribute('fill', 'none');
|
||||
svg.style.filter = 'drop-shadow(0 1px 2px rgba(0,0,0,0.2))';
|
||||
|
||||
const path = document.createElementNS(svgNS, 'path');
|
||||
path.setAttribute('fill-rule', 'evenodd');
|
||||
path.setAttribute('clip-rule', 'evenodd');
|
||||
path.setAttribute('d', 'M25.061 6.90625L23.7115 8.25503C23.2861 8.05188 22.8241 7.9503 22.3621 7.9503C21.5605 7.9503 20.7589 8.25613 20.1483 8.86778L8.18147 20.8336L6.70565 26.7379H6.70557V27.7817H26.5372V26.7379H6.70671L12.6102 25.2623L24.576 13.2955C25.5404 12.3318 25.7445 10.8952 25.1882 9.73146L26.5369 8.38214L25.061 6.90625ZM23.174 10.27C22.9569 10.0539 22.6688 9.93388 22.362 9.93388C22.0551 9.93388 21.767 10.0539 21.5499 10.27L13.5323 18.2876L15.1564 19.9117L23.174 11.8941C23.6218 11.4463 23.6218 10.7177 23.174 10.27ZM14.4922 20.5759L12.868 18.9518L9.97241 21.8475L9.43069 24.0133L11.5965 23.4716L14.4922 20.5759Z');
|
||||
path.setAttribute('fill', 'white');
|
||||
|
||||
svg.appendChild(path);
|
||||
button.appendChild(textDiv);
|
||||
button.appendChild(svg);
|
||||
|
||||
// Add hover effect
|
||||
button.addEventListener('mouseenter', () => {
|
||||
button.style.backgroundColor = '#4338CA';
|
||||
button.style.transform = 'scale(1.05)';
|
||||
button.style.boxShadow = '0 4px 12px rgba(79, 70, 229, 0.4)';
|
||||
});
|
||||
button.addEventListener('mouseleave', () => {
|
||||
button.style.backgroundColor = '#4F46E5';
|
||||
button.style.transform = 'scale(1)';
|
||||
button.style.boxShadow = '0 2px 8px rgba(0,0,0,0.15)';
|
||||
});
|
||||
|
||||
// Add click handler
|
||||
button.addEventListener('click', () => {
|
||||
if (this.dotNetReference) {
|
||||
this.dotNetReference.invokeMethodAsync('OnSignatureButtonClick', sig.id);
|
||||
}
|
||||
});
|
||||
|
||||
signatureLayer.appendChild(button);
|
||||
this.signatureButtons.push(button);
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error rendering signature buttons:', error);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Clears all signature buttons from the canvas.
|
||||
*/
|
||||
clearSignatureButtons() {
|
||||
this.signatureButtons.forEach(button => {
|
||||
if (button.parentNode) {
|
||||
button.parentNode.removeChild(button);
|
||||
}
|
||||
});
|
||||
this.signatureButtons = [];
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user