diff --git a/EnvelopeGenerator.ReceiverUI/Pages/EnvelopeViewer.razor b/EnvelopeGenerator.ReceiverUI/Pages/EnvelopeViewer.razor index a164c612..bbeaecf1 100644 --- a/EnvelopeGenerator.ReceiverUI/Pages/EnvelopeViewer.razor +++ b/EnvelopeGenerator.ReceiverUI/Pages/EnvelopeViewer.razor @@ -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("pdfViewer.getSignatureNavState"); diff --git a/EnvelopeGenerator.ReceiverUI/wwwroot/js/pdf-viewer.js b/EnvelopeGenerator.ReceiverUI/wwwroot/js/pdf-viewer.js index 33beb6f1..e4219f34 100644 --- a/EnvelopeGenerator.ReceiverUI/wwwroot/js/pdf-viewer.js +++ b/EnvelopeGenerator.ReceiverUI/wwwroot/js/pdf-viewer.js @@ -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 + } + } + }); + } }, /**