Improve signature navigation and rendering stability
Enhanced signature navigation and rendering logic in `pdf-viewer.js`: - Added `_renderLock` to prevent concurrent page renders. - Refactored `renderPage` and `queueRenderPage` for stability. - Updated `goToNextSignature` to support cross-page navigation. - Filtered out applied signatures during rendering and navigation. - Improved handling of applied signatures visibility per page. Updated `EnvelopeViewer.razor`: - Added `OnPageChangedBySignatureNav` to handle page changes triggered by signature navigation. Improved code readability, added comments, and removed outdated logic to ensure smooth transitions and better user experience.
This commit is contained in:
@@ -616,6 +616,12 @@ const int MaxThumbnailWidth = 400;
|
||||
await UpdateSignatureCounterAsync();
|
||||
}
|
||||
|
||||
[JSInvokable]
|
||||
public async Task OnPageChangedBySignatureNav(int newPage) {
|
||||
_currentPage = newPage;
|
||||
await RenderSignatureButtonsAsync();
|
||||
}
|
||||
|
||||
async Task UpdateSignatureCounterAsync() {
|
||||
try {
|
||||
var state = await JSRuntime.InvokeAsync<SignatureNavState>("pdfViewer.getSignatureNavState");
|
||||
|
||||
@@ -11,6 +11,7 @@ window.pdfViewer = {
|
||||
currentRenderTask: null,
|
||||
dotNetReference: null,
|
||||
wheelEventAttached: false,
|
||||
_renderLock: false, // Lock to prevent concurrent renders
|
||||
|
||||
// Quality options (configurable from appsettings.json)
|
||||
qualityOptions: {
|
||||
@@ -119,6 +120,26 @@ window.pdfViewer = {
|
||||
},
|
||||
|
||||
async renderPage(num) {
|
||||
// CRITICAL: Single render at a time - use a lock
|
||||
if (this._renderLock) {
|
||||
// Another render is in progress, queue it
|
||||
this.pageNumPending = num;
|
||||
return;
|
||||
}
|
||||
|
||||
this._renderLock = true;
|
||||
|
||||
// Cancel any existing render task
|
||||
if (this.currentRenderTask) {
|
||||
try {
|
||||
this.currentRenderTask.cancel();
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
} catch (e) {
|
||||
// Ignore cancellation errors
|
||||
}
|
||||
this.currentRenderTask = null;
|
||||
}
|
||||
|
||||
this.pageRendering = true;
|
||||
|
||||
// Add rendering class for smooth transition (if enabled)
|
||||
@@ -164,14 +185,11 @@ window.pdfViewer = {
|
||||
viewport: viewport
|
||||
};
|
||||
|
||||
if (this.currentRenderTask) {
|
||||
this.currentRenderTask.cancel();
|
||||
}
|
||||
|
||||
// Enable high-quality rendering
|
||||
this.ctx.imageSmoothingEnabled = true;
|
||||
this.ctx.imageSmoothingQuality = 'high';
|
||||
|
||||
// Start new render task
|
||||
this.currentRenderTask = page.render(renderContext);
|
||||
await this.currentRenderTask.promise;
|
||||
|
||||
@@ -198,10 +216,6 @@ window.pdfViewer = {
|
||||
this.currentRenderTask = null;
|
||||
this.pageRendering = false;
|
||||
|
||||
if (this.pageNumPending !== null) {
|
||||
this.renderPage(this.pageNumPending);
|
||||
this.pageNumPending = null;
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.name !== 'RenderingCancelledException') {
|
||||
console.error('Render error:', error);
|
||||
@@ -209,6 +223,16 @@ window.pdfViewer = {
|
||||
this.canvas.classList.remove('rendering');
|
||||
this.currentRenderTask = null;
|
||||
this.pageRendering = false;
|
||||
} finally {
|
||||
// Always release lock
|
||||
this._renderLock = false;
|
||||
|
||||
// Process pending render
|
||||
if (this.pageNumPending !== null) {
|
||||
const pendingPage = this.pageNumPending;
|
||||
this.pageNumPending = null;
|
||||
this.renderPage(pendingPage);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -250,7 +274,8 @@ window.pdfViewer = {
|
||||
},
|
||||
|
||||
queueRenderPage(num) {
|
||||
if (this.pageRendering) {
|
||||
// Always use pending mechanism to avoid race conditions
|
||||
if (this.pageRendering || this.currentRenderTask) {
|
||||
this.pageNumPending = num;
|
||||
} else {
|
||||
this.renderPage(num);
|
||||
@@ -492,41 +517,55 @@ window.pdfViewer = {
|
||||
/**
|
||||
* Navigates to the next unsigned signature button.
|
||||
* Scrolls to button position and changes page if necessary.
|
||||
* Cross-page navigation: searches ALL pages for next unsigned signature.
|
||||
*/
|
||||
async goToNextSignature(dotNetRef) {
|
||||
if (this.signatureButtons.length === 0) {
|
||||
// Global imza listesi yoksa ç?k
|
||||
if (!this._allSignatures || this._allSignatures.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get first unsigned signature button
|
||||
const button = this.signatureButtons[0];
|
||||
const signatureId = parseInt(button.getAttribute('data-signature-id'));
|
||||
// TÜM sayfalar aras?nda ilk imzalanmam?? imzay? bul
|
||||
const appliedIds = new Set(this.appliedSignatures.map(s => s.id));
|
||||
const nextSignature = this._allSignatures.find(sig => !appliedIds.has(sig.id));
|
||||
|
||||
// Find signature in original list to get page number
|
||||
const signature = this._allSignatures?.find(s => s.id === signatureId);
|
||||
if (!signature) {
|
||||
return false;
|
||||
if (!nextSignature) {
|
||||
return false; // Hepsi imzalanm??
|
||||
}
|
||||
|
||||
// Change page if needed
|
||||
if (signature.page !== this.pageNum) {
|
||||
await this.goToPage(signature.page);
|
||||
// Farkl? sayfadaysa sayfa de?i?tir
|
||||
if (nextSignature.page !== this.pageNum) {
|
||||
// Sayfa de?i?tir
|
||||
this.pageNum = nextSignature.page;
|
||||
this.queueRenderPage(this.pageNum);
|
||||
|
||||
// Wait for page render and button re-creation
|
||||
await new Promise(resolve => setTimeout(resolve, 300));
|
||||
|
||||
// Re-find button after page change
|
||||
const newButton = this.signatureButtons.find(btn =>
|
||||
parseInt(btn.getAttribute('data-signature-id')) === signatureId);
|
||||
|
||||
if (newButton) {
|
||||
this.scrollToButton(newButton);
|
||||
// Render tamamlanana kadar bekle
|
||||
let waitCount = 0;
|
||||
while (this.pageRendering && waitCount < 20) {
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
waitCount++;
|
||||
}
|
||||
} else {
|
||||
|
||||
// Blazor'a haber ver - signature butonlar?n? yeniden ?iz
|
||||
if (dotNetRef) {
|
||||
await dotNetRef.invokeMethodAsync('OnPageChangedBySignatureNav', this.pageNum);
|
||||
}
|
||||
|
||||
// Butonlar?n DOM'a eklenmesini bekle
|
||||
await new Promise(resolve => setTimeout(resolve, 150));
|
||||
}
|
||||
|
||||
// Mevcut sayfadaki (art?k yeni sayfa olabilir) butonu bul
|
||||
const button = this.signatureButtons.find(btn =>
|
||||
parseInt(btn.getAttribute('data-signature-id')) === nextSignature.id
|
||||
);
|
||||
|
||||
// Butonu görünür hale getir (scroll ile)
|
||||
if (button) {
|
||||
this.scrollToButton(button);
|
||||
}
|
||||
|
||||
// Notify Blazor to update counter
|
||||
// Counter'? güncelle (Blazor'a bildir)
|
||||
if (dotNetRef) {
|
||||
dotNetRef.invokeMethodAsync('OnSignatureNavChanged');
|
||||
}
|
||||
@@ -548,8 +587,24 @@ window.pdfViewer = {
|
||||
|
||||
// Change page if needed
|
||||
if (lastSig.page !== this.pageNum) {
|
||||
await this.goToPage(lastSig.page);
|
||||
await new Promise(resolve => setTimeout(resolve, 300));
|
||||
// Sayfa de?i?tir
|
||||
this.pageNum = lastSig.page;
|
||||
this.queueRenderPage(this.pageNum);
|
||||
|
||||
// Render tamamlanana kadar bekle
|
||||
let waitCount = 0;
|
||||
while (this.pageRendering && waitCount < 20) {
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
waitCount++;
|
||||
}
|
||||
|
||||
// Blazor'a haber ver - signature butonlar?n? yeniden çiz
|
||||
if (dotNetRef) {
|
||||
await dotNetRef.invokeMethodAsync('OnPageChangedBySignatureNav', this.pageNum);
|
||||
}
|
||||
|
||||
// DOM güncellenmesini bekle
|
||||
await new Promise(resolve => setTimeout(resolve, 150));
|
||||
}
|
||||
|
||||
// Find applied signature container
|
||||
@@ -614,7 +669,7 @@ window.pdfViewer = {
|
||||
* @param {object} dotNetRef - .NET reference for callbacks
|
||||
*/
|
||||
async renderSignatureButtons(signatures, currentPageNum, dotNetRef) {
|
||||
// Clear existing buttons
|
||||
// Clear existing buttons (NOT applied signatures!)
|
||||
this.clearSignatureButtons();
|
||||
|
||||
if (!this.pdfDoc || !signatures || signatures.length === 0) {
|
||||
@@ -625,8 +680,11 @@ window.pdfViewer = {
|
||||
this._allSignatures = signatures; // Store for navigation
|
||||
|
||||
try {
|
||||
// Filter signatures for current page
|
||||
const pageSignatures = signatures.filter(sig => sig.page === currentPageNum);
|
||||
// CRITICAL: Filter OUT already applied signatures!
|
||||
const appliedIds = new Set(this.appliedSignatures.map(s => s.id));
|
||||
const pageSignatures = signatures.filter(sig =>
|
||||
sig.page === currentPageNum && !appliedIds.has(sig.id) // ? Skip applied ones!
|
||||
);
|
||||
|
||||
if (pageSignatures.length === 0) {
|
||||
return;
|
||||
@@ -650,7 +708,7 @@ window.pdfViewer = {
|
||||
signatureLayer.style.width = `${viewport.width / dpr}px`;
|
||||
signatureLayer.style.height = `${viewport.height / dpr}px`;
|
||||
|
||||
// Create button for each signature
|
||||
// Create button for each UNSIGNED signature
|
||||
pageSignatures.forEach(sig => {
|
||||
// Coordinates are in PDF POINTS - convert to display pixels
|
||||
const xPx = (sig.x * this.scale);
|
||||
@@ -738,14 +796,35 @@ window.pdfViewer = {
|
||||
|
||||
/**
|
||||
* Clears all signature buttons from the canvas.
|
||||
* Also hides applied signatures that don't belong to current page.
|
||||
*/
|
||||
clearSignatureButtons() {
|
||||
// Remove unsigned signature buttons
|
||||
this.signatureButtons.forEach(button => {
|
||||
if (button.parentNode) {
|
||||
button.parentNode.removeChild(button);
|
||||
}
|
||||
});
|
||||
this.signatureButtons = [];
|
||||
|
||||
// Hide/show applied signatures based on current page
|
||||
const signatureLayer = document.getElementById('pdf-signature-layer');
|
||||
if (signatureLayer) {
|
||||
const appliedContainers = signatureLayer.querySelectorAll('.applied-signature');
|
||||
appliedContainers.forEach(container => {
|
||||
const signatureId = parseInt(container.getAttribute('data-signature-id'));
|
||||
const signature = this._allSignatures?.find(s => s.id === signatureId);
|
||||
|
||||
if (signature) {
|
||||
// Show only if on current page, hide otherwise
|
||||
if (signature.page === this.pageNum) {
|
||||
container.style.display = ''; // Show
|
||||
} else {
|
||||
container.style.display = 'none'; // Hide
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user