From cc1d33462c8e683a6df52cfe5925bea0d4427a9b Mon Sep 17 00:00:00 2001 From: OlgunR Date: Tue, 9 Dec 2025 10:42:11 +0100 Subject: [PATCH] Add drag-and-drop PDF support to Blazor app Implement drag-and-drop PDF loading via JS interop and DotNetObjectReference. Refactor file loading logic and UI structure for clarity. Add IAsyncDisposable for resource cleanup. Update pdfInterop.js to handle drop events and send PDF data to Blazor. --- .../Pages/Index.razor | 175 ++++++++++-------- .../wwwroot/js/pdfInterop.js | 33 ++++ 2 files changed, 135 insertions(+), 73 deletions(-) diff --git a/EnvelopeGenerator.ReceiverUIBlazor/Pages/Index.razor b/EnvelopeGenerator.ReceiverUIBlazor/Pages/Index.razor index 3228d138..fea7170e 100644 --- a/EnvelopeGenerator.ReceiverUIBlazor/Pages/Index.razor +++ b/EnvelopeGenerator.ReceiverUIBlazor/Pages/Index.razor @@ -1,65 +1,69 @@ @page "/" +@using Microsoft.JSInterop @inject IJSRuntime JS +@implements IAsyncDisposable + +
+

Sign PDF (Blazor)

+ +
+ + + + + + +
-

Sign PDF (Blazor)

- -
- - - - - - -
- -@if (!string.IsNullOrWhiteSpace(ErrorMessage)) -{ -
@ErrorMessage
-} + @if (!string.IsNullOrWhiteSpace(ErrorMessage)) + { +
@ErrorMessage
+ } -@if (!HasPdf) -{ -
Drop or select a PDF to start.
-} + @if (!HasPdf) + { +
Drop or select a PDF to start.
+ } -@if (HasPdf) -{ -
- + @if (HasPdf) + { +
+ - @if (ShowSignature) - { -
-
- - + @if (ShowSignature) + { +
+
+ + +
+
- -
- } + } - @if (ShowText) - { -
-
- - + @if (ShowText) + { +
+
+ + +
+
- -
- } -
+ } +
-
- - Page @DisplayPage / @PageCount - -
-} +
+ + Page @DisplayPage / @PageCount + +
+ } +
@if (ShowSignaturePadModal) { @@ -85,6 +89,7 @@ private string? PdfBase64; private string? OriginalPdfBase64; + private DotNetObjectReference? _dotNetRef; private int PageIndex; private int PageCount; private double ViewportWidthPx = 800; @@ -120,6 +125,8 @@ if (firstRender) { await JS.InvokeVoidAsync("pdfInterop.ensureReady"); + _dotNetRef ??= DotNetObjectReference.Create(this); + await JS.InvokeVoidAsync("pdfInterop.registerDropHandler", _dotNetRef); } if (ShowSignaturePadModal) @@ -129,35 +136,25 @@ } private async Task HandleFileSelected(InputFileChangeEventArgs e) + { + if (e.FileCount == 0) + { + return; + } + + await LoadPdfFromBrowserFile(e.File); + } + + private async Task LoadPdfFromBrowserFile(IBrowserFile file) { ErrorMessage = null; try { - if (e.FileCount == 0) - { - return; - } - - var file = e.File; await using var stream = file.OpenReadStream(maxAllowedSize: 20 * 1024 * 1024); using var ms = new MemoryStream(); await stream.CopyToAsync(ms); - PdfBase64 = Convert.ToBase64String(ms.ToArray()); - OriginalPdfBase64 = PdfBase64; - - // Show the canvas before we start rendering - await InvokeAsync(StateHasChanged); - await Task.Yield(); - - // Make sure pdf.js is ready - await JS.InvokeVoidAsync("pdfInterop.ensureReady"); - - var result = await JS.InvokeAsync("pdfInterop.loadPdf", PdfBase64); - PageCount = result.Pages; - PageIndex = 0; - - await RenderPage(); + await LoadPdfFromBase64Internal(Convert.ToBase64String(ms.ToArray())); } catch (Exception ex) { @@ -168,6 +165,32 @@ } } + [JSInvokable] + public Task LoadPdfFromBase64(string base64) + { + return LoadPdfFromBase64Internal(base64); + } + + private async Task LoadPdfFromBase64Internal(string base64) + { + ErrorMessage = null; + PdfBase64 = base64; + OriginalPdfBase64 = PdfBase64; + + // Show the canvas before we start rendering + await InvokeAsync(StateHasChanged); + await Task.Yield(); + + // Make sure pdf.js is ready + await JS.InvokeVoidAsync("pdfInterop.ensureReady"); + + var result = await JS.InvokeAsync("pdfInterop.loadPdf", PdfBase64); + PageCount = result.Pages; + PageIndex = 0; + + await RenderPage(); + } + private async Task RenderPage() { if (!HasPdf) @@ -396,4 +419,10 @@ private record RenderResult(int Pages); private record ViewportInfo(double Width, double Height, double PageWidth, double PageHeight); + + public async ValueTask DisposeAsync() + { + _dotNetRef?.Dispose(); + await Task.CompletedTask; + } } diff --git a/EnvelopeGenerator.ReceiverUIBlazor/wwwroot/js/pdfInterop.js b/EnvelopeGenerator.ReceiverUIBlazor/wwwroot/js/pdfInterop.js index 6d0ea97f..8d559304 100644 --- a/EnvelopeGenerator.ReceiverUIBlazor/wwwroot/js/pdfInterop.js +++ b/EnvelopeGenerator.ReceiverUIBlazor/wwwroot/js/pdfInterop.js @@ -264,6 +264,39 @@ pointerPads.set(canvasId, { ctx, canvas }); }, + registerDropHandler: (dotNetRef) => { + if (window.__pdfDropRegistered) return; + window.__pdfDropRegistered = true; + + const prevent = (e) => { + e.preventDefault(); + e.stopPropagation(); + }; + + ['dragenter', 'dragover', 'dragleave'].forEach(evt => { + document.addEventListener(evt, prevent, false); + }); + + document.addEventListener('drop', (e) => { + prevent(e); + + const files = e.dataTransfer?.files; + if (!files || files.length === 0) { + return; + } + + const file = files[0]; + const reader = new FileReader(); + reader.onload = () => { + const result = reader.result; + if (typeof result === 'string') { + const base64 = result.split(',')[1] || result; + dotNetRef?.invokeMethodAsync('LoadPdfFromBase64', base64); + } + }; + reader.readAsDataURL(file); + }, false); + }, clearSignaturePad: (canvasId) => { const pad = pointerPads.get(canvasId); if (!pad) return;