From 41f3df4c718fe68bbc1cd2c05024485714a476af Mon Sep 17 00:00:00 2001 From: TekH Date: Fri, 5 Jun 2026 12:36:59 +0200 Subject: [PATCH] Enable Blazor-JS communication for PDF viewer zoom Added `DotNetObjectReference` in `EnvelopeViewer.razor` to enable Blazor-JS communication. Updated `OnAfterRenderAsync` to pass the reference to `pdfViewer.initialize`. Introduced `[JSInvokable]` method `OnZoomChanged` to handle zoom updates from JavaScript. Enhanced `DisposeAsync` to clean up resources, including disposing of the `.NET reference` and invoking a JavaScript `dispose` method. In `pdf-viewer.js`, modified `initialize` to accept a `.NET reference` and added `attachWheelEvent` to handle zooming via mouse wheel with `Ctrl`/`Meta` key. Updated `dispose` to clean up event listeners and reset the `.NET reference`. Added `getScale` to retrieve the current zoom scale. Improved resource cleanup and event handling to prevent memory leaks and ensure stability. --- .../Pages/EnvelopeViewer.razor | 20 +++++- .../wwwroot/js/pdf-viewer.js | 68 ++++++++++++++++--- 2 files changed, 76 insertions(+), 12 deletions(-) diff --git a/EnvelopeGenerator.ReceiverUI/Pages/EnvelopeViewer.razor b/EnvelopeGenerator.ReceiverUI/Pages/EnvelopeViewer.razor index 300e6b89..1c8c3061 100644 --- a/EnvelopeGenerator.ReceiverUI/Pages/EnvelopeViewer.razor +++ b/EnvelopeGenerator.ReceiverUI/Pages/EnvelopeViewer.razor @@ -117,6 +117,7 @@ int _currentPage = 1; int _totalPages = 0; int _currentZoom = 150; + DotNetObjectReference? _dotNetRef; protected override async Task OnInitializedAsync() { if (string.IsNullOrWhiteSpace(EnvelopeKey)) { @@ -147,7 +148,8 @@ await Task.Delay(500); try { - var success = await JSRuntime.InvokeAsync("pdfViewer.initialize", "pdf-canvas", _pdfDataUrl); + _dotNetRef = DotNetObjectReference.Create(this); + var success = await JSRuntime.InvokeAsync("pdfViewer.initialize", "pdf-canvas", _pdfDataUrl, _dotNetRef); if (success) { _pdfLoaded = true; @@ -162,6 +164,13 @@ } } + [JSInvokable] + public async Task OnZoomChanged(double scale) + { + _currentZoom = (int)(scale * 100); + await InvokeAsync(StateHasChanged); + } + async Task NextPage() { if (await JSRuntime.InvokeAsync("pdfViewer.nextPage")) { _currentPage = await JSRuntime.InvokeAsync("pdfViewer.getCurrentPage"); @@ -187,6 +196,13 @@ } public async ValueTask DisposeAsync() { - // Cleanup if needed + if (_pdfLoaded) { + try { + await JSRuntime.InvokeVoidAsync("pdfViewer.dispose"); + } catch { + // Ignore errors during disposal + } + } + _dotNetRef?.Dispose(); } } diff --git a/EnvelopeGenerator.ReceiverUI/wwwroot/js/pdf-viewer.js b/EnvelopeGenerator.ReceiverUI/wwwroot/js/pdf-viewer.js index c1782bc3..871c801f 100644 --- a/EnvelopeGenerator.ReceiverUI/wwwroot/js/pdf-viewer.js +++ b/EnvelopeGenerator.ReceiverUI/wwwroot/js/pdf-viewer.js @@ -1,19 +1,24 @@ // PDF.js Viewer for Blazor WASM window.pdfViewer = { -pdfDoc: null, -pageNum: 1, -pageRendering: false, -pageNumPending: null, -scale: 1.5, -canvas: null, -ctx: null, -totalPages: 0, -currentRenderTask: null, + pdfDoc: null, + pageNum: 1, + pageRendering: false, + pageNumPending: null, + scale: 1.5, + canvas: null, + ctx: null, + totalPages: 0, + currentRenderTask: null, + dotNetReference: null, + wheelEventAttached: false, - async initialize(canvasId, pdfDataUrl) { + async initialize(canvasId, pdfDataUrl, dotNetRef) { try { console.log('PDF.js initialization started for canvas:', canvasId); + // Store .NET reference for callbacks + this.dotNetReference = dotNetRef; + // Wait for PDF.js to load if (typeof window.pdfjsLib === 'undefined') { console.error('PDF.js library not loaded, waiting...'); @@ -33,6 +38,9 @@ currentRenderTask: null, this.ctx = this.canvas.getContext('2d'); + // Attach mouse wheel event listener + this.attachWheelEvent(); + // Load PDF from data URL const uint8Array = this.base64ToUint8Array(pdfDataUrl); console.log('PDF data converted to Uint8Array, size:', uint8Array.length); @@ -52,6 +60,35 @@ currentRenderTask: null, } }, + attachWheelEvent() { + if (this.wheelEventAttached) return; + + // Attach to the entire document body for global zoom control + document.body.addEventListener('wheel', (e) => { + // Check if Ctrl key is pressed (zoom gesture) + if (e.ctrlKey || e.metaKey) { + e.preventDefault(); + + if (e.deltaY < 0) { + // Scroll up = Zoom In + this.zoomIn(); + if (this.dotNetReference) { + this.dotNetReference.invokeMethodAsync('OnZoomChanged', this.scale); + } + } else { + // Scroll down = Zoom Out + this.zoomOut(); + if (this.dotNetReference) { + this.dotNetReference.invokeMethodAsync('OnZoomChanged', this.scale); + } + } + } + }, { passive: false }); + + this.wheelEventAttached = true; + console.log('Wheel event listener attached to document body'); + }, + async waitForPdfJs() { for (let i = 0; i < 50; i++) { if (typeof window.pdfjsLib !== 'undefined') { @@ -176,6 +213,17 @@ currentRenderTask: null, getScale() { return this.scale; + }, + + dispose() { + // Clean up event listeners + if (this.wheelEventAttached) { + // Note: We can't remove the exact listener without keeping a reference + // but we can at least mark it as disposed + this.wheelEventAttached = false; + this.dotNetReference = null; + console.log('PDF viewer disposed'); + } } };