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:
2026-06-07 23:28:50 +02:00
parent c76ddb7123
commit 2cea284a9d
2 changed files with 122 additions and 37 deletions

View File

@@ -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");

View File

@@ -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
}
}
});
}
},
/**