From 6aa97adf84d627e09a7a2f146d495d8e5ee2bf8c Mon Sep 17 00:00:00 2001 From: TekH Date: Thu, 25 Jun 2026 13:10:32 +0200 Subject: [PATCH] Refactor and enhance EnvelopeReceiverPage UI/UX - Replaced `SignatureService` with `DocReceiverElementService` in DI. - Refactored `envelope-action-bar` for better readability and added badges for `2FA`, `Access Code`, and `Signature Count`. - Improved error handling and loading states with clearer messages. - Enhanced PDF viewer toolbar with better navigation, zoom, and signature controls. - Added resizable thumbnail sidebar with persistent width settings. - Refactored signature popup to support draw, text, and image tabs with validation. - Improved JavaScript interop for PDF rendering and signature handling. - Introduced DevExpress PDF Viewer as an alternative implementation. - Consolidated state management and improved code readability. --- .../Pages/EnvelopeReceiverPage.razor | 842 ++++++++++-------- .../EnvelopeReceiverPage_DxPdfViewer.razor | 1 + 2 files changed, 490 insertions(+), 353 deletions(-) diff --git a/EnvelopeGenerator.Server/EnvelopeGenerator.Server/Components/Pages/EnvelopeReceiverPage.razor b/EnvelopeGenerator.Server/EnvelopeGenerator.Server/Components/Pages/EnvelopeReceiverPage.razor index 3e3cfb87..587f8b06 100644 --- a/EnvelopeGenerator.Server/EnvelopeGenerator.Server/Components/Pages/EnvelopeReceiverPage.razor +++ b/EnvelopeGenerator.Server/EnvelopeGenerator.Server/Components/Pages/EnvelopeReceiverPage.razor @@ -12,7 +12,7 @@ @inject IOptions AppOptions @inject IOptions PdfViewerOptions @inject IJSRuntime JSRuntime -@inject SignatureService SignatureService +@inject DocReceiverElementService SignatureService @inject SignatureCacheService SignatureCacheService @inject EnvelopeGenerator.Server.Client.Services.AuthService AuthService @inject EnvelopeGenerator.Server.Client.Services.EnvelopeReceiverService EnvelopeReceiverService @@ -28,122 +28,143 @@
-
-
- @* Row 1: Title + Sender + Badges + Logout *@ -
- @* Left: Title + Sender *@ -
- @if (_envelopeReceiver is not null) { -
- @(_envelopeReceiver.Envelope?.Title ?? "Dokument") -
- @if (!string.IsNullOrWhiteSpace(_envelopeReceiver.Envelope?.User?.FullName) || !string.IsNullOrWhiteSpace(_envelopeReceiver.Envelope?.User?.Email)) { - - Von - @if (!string.IsNullOrWhiteSpace(_envelopeReceiver.Envelope?.User?.FullName)) { - @_envelopeReceiver.Envelope.User.FullName - } - @if (!string.IsNullOrWhiteSpace(_envelopeReceiver.Envelope?.User?.Email)) { - <@_envelopeReceiver.Envelope.User.Email> - } - @if (_envelopeReceiver.Envelope?.AddedWhen != null) { -  · @_envelopeReceiver.Envelope.AddedWhen.ToString("dd.MM.yyyy") - } - +
+
+ @* Row 1: Title + Sender + Badges + Logout *@ +
+ @* Left: Title + Sender *@ +
+ @if (_envelopeReceiver is not null) + { +
+ @(_envelopeReceiver.Envelope?.Title ?? "Dokument") +
+ @if (!string.IsNullOrWhiteSpace(_envelopeReceiver.Envelope?.User?.FullName) || !string.IsNullOrWhiteSpace(_envelopeReceiver.Envelope?.User?.Email)) + { + + Von + @if (!string.IsNullOrWhiteSpace(_envelopeReceiver.Envelope?.User?.FullName)) + { + @_envelopeReceiver.Envelope.User.FullName + } + @if (!string.IsNullOrWhiteSpace(_envelopeReceiver.Envelope?.User?.Email)) + { + <@_envelopeReceiver.Envelope.User.Email> + } + @if (_envelopeReceiver.Envelope?.AddedWhen != null) + { +  · @_envelopeReceiver.Envelope.AddedWhen.ToString("dd.MM.yyyy") + } + + } } - } else { -
Dokumentenansicht
- } -
- - @* Right: Badges + Logout *@ -
- @if (_envelopeReceiver is not null) { -
- @if (!string.IsNullOrWhiteSpace(_envelopeReceiver.Name)) { - - - - - @_envelopeReceiver.Name - - } - @if (!string.IsNullOrWhiteSpace(_envelopeReceiver.Envelope?.User?.FullName)) { - - Von @_envelopeReceiver.Envelope.User.FullName - - } - @{ - int sigCount = _signatures.Count; - } - @if (sigCount > 0) { - - - - - @sigCount - - } - @if (_envelopeReceiver.Envelope?.UseAccessCode == true) { - - - - - Code - - } - @if (_envelopeReceiver.Envelope?.TFAEnabled == true) { - - - - - - 2FA - + else + { +
Dokumentenansicht
}
+ @* Right: Badges + Logout *@ +
+ @if (_envelopeReceiver is not null) + { +
+ @if (!string.IsNullOrWhiteSpace(_envelopeReceiver.Name)) + { + + + + + @_envelopeReceiver.Name + + } + @if (!string.IsNullOrWhiteSpace(_envelopeReceiver.Envelope?.User?.FullName)) + { + + Von @_envelopeReceiver.Envelope.User.FullName + + } + @{ + int sigCount = _signatures.Count; + } + @if (sigCount > 0) + { + + + + + @sigCount + + } + @if (_envelopeReceiver.Envelope?.UseAccessCode ?? false) + { + + + + + Code + + } + @if (_envelopeReceiver.Envelope?.TFAEnabled ?? false) + { + + + + + + 2FA + + } +
+ + } + + @* Logout button *@ + @if (!string.IsNullOrWhiteSpace(EnvelopeKey)) + { + + } +
+
+ + @* Row 2: Messages (visible text) *@ + @if (_envelopeReceiver is not null && (!string.IsNullOrWhiteSpace(_envelopeReceiver.Envelope?.Message) || !string.IsNullOrWhiteSpace(_envelopeReceiver.PrivateMessage))) + { +
+ @if (!string.IsNullOrWhiteSpace(_envelopeReceiver.Envelope?.Message)) + { +
+ 📧 + @_envelopeReceiver.Envelope.Message +
+ } + @if (!string.IsNullOrWhiteSpace(_envelopeReceiver.PrivateMessage)) + { +
+ 🔒 + @_envelopeReceiver.PrivateMessage +
+ } +
} - - @* Logout button *@ - @if (!string.IsNullOrWhiteSpace(EnvelopeKey)) { - - } -
- - @* Row 2: Messages (visible text) *@ - @if (_envelopeReceiver is not null && (!string.IsNullOrWhiteSpace(_envelopeReceiver.Envelope?.Message) || !string.IsNullOrWhiteSpace(_envelopeReceiver.PrivateMessage))) { -
- @if (!string.IsNullOrWhiteSpace(_envelopeReceiver.Envelope?.Message)) { -
- 📧 - @_envelopeReceiver.Envelope.Message -
- } - @if (!string.IsNullOrWhiteSpace(_envelopeReceiver.PrivateMessage)) { -
- 🔒 - @_envelopeReceiver.PrivateMessage -
- } -
- }
-
-
- @if (_isLoading) { +
+ @if (_isLoading) + {
@@ -152,13 +173,15 @@

Dokument wird geladen...

- } else if (_errorMessage is not null) { + } + else if (_errorMessage is not null) + {
- - + +
Fehler beim Laden des Dokuments
@@ -167,14 +190,17 @@
- } else if (!string.IsNullOrWhiteSpace(_pdfDataUrl)) { + } + else if (!string.IsNullOrWhiteSpace(_pdfDataUrl)) + {
- @if (_pdfLoaded) { + @if (_pdfLoaded) + {
@@ -184,7 +210,7 @@
@@ -193,7 +219,7 @@
@@ -203,7 +229,7 @@
@@ -212,21 +238,22 @@
- @if (_totalSignatures > 0) { + @if (_totalSignatures > 0) + {
- @@ -235,21 +262,22 @@
- - +
- + - @if (_currentSignatureIndex > 0) { + @if (_currentSignatureIndex > 0) + { #@_currentSignatureIndex | } @@ -257,19 +285,22 @@  /  @_totalSignatures - @if (_unsignedSignatures > 0) { + @if (_unsignedSignatures > 0) + { @_unsignedSignatures offen - } else { + } + else + { ✓ Komplett }
- -
@@ -277,14 +308,15 @@
@* Reset button - only show when signatures are signed *@ - @if (_signedSignatures > 0) { + @if (_signedSignatures > 0) + {
-
@@ -293,11 +325,13 @@
}
- @if (_pdfLoaded && _showThumbnails) { + @if (_pdfLoaded && _showThumbnails) + {
- @for (int i = 1; i <= _totalPages; i++) { + @for (int i = 1; i <= _totalPages; i++) + { var pageNum = i;
@@ -309,7 +343,7 @@
-
@@ -323,12 +357,14 @@
- } else { + } + else + {
- + Dokument konnte nicht geladen werden.
@@ -339,26 +375,26 @@
+ HeaderText="Unterschrift erstellen" + Width="620px" + MaxWidth="95vw" + ShowFooter="true" + CloseOnOutsideClick="false" + ShowCloseButton="false" + CloseOnEscape="false" + Shown="OnPopupShownAsync"> - @if(_activeSignatureTab == SignatureTabDraw) { + @if (_activeSignatureTab == SignatureTabDraw) + {

Bitte unterschreiben Sie im folgenden Feld.

- - } else if(_activeSignatureTab == SignatureTabText) { + } + else if (_activeSignatureTab == SignatureTabText) + {

Geben Sie Ihre Unterschrift als Text ein und wählen Sie eine Schriftart.

-
-
- - } else { + } + else + {

Laden Sie ein Bild Ihrer Unterschrift hoch.

- - } @@ -425,9 +467,9 @@ Shown="OnPopupShownAsync"> -
@@ -435,9 +477,9 @@ Shown="OnPopupShownAsync"> -
@@ -445,16 +487,17 @@ Shown="OnPopupShownAsync"> -
- @if(!string.IsNullOrWhiteSpace(_popupValidationMessage)) { + @if (!string.IsNullOrWhiteSpace(_popupValidationMessage)) + {
@_popupValidationMessage
@@ -462,20 +505,20 @@ Shown="OnPopupShownAsync">
- - @@ -484,16 +527,16 @@ Shown="OnPopupShownAsync"> @code { -// Signature tab constants -const string SignatureTabDraw = "draw"; -const string SignatureTabText = "text"; -const string SignatureTabImage = "image"; -const string DrawCanvasId = "envelope-signature-pad"; -const string TypedCanvasId = "envelope-typed-signature-pad"; -const string ImageInputId = "envelope-signature-image-input"; -const string ImageCanvasId = "envelope-image-signature-pad"; + // Signature tab constants + const string SignatureTabDraw = "draw"; + const string SignatureTabText = "text"; + const string SignatureTabImage = "image"; + const string DrawCanvasId = "envelope-signature-pad"; + const string TypedCanvasId = "envelope-typed-signature-pad"; + const string ImageInputId = "envelope-signature-image-input"; + const string ImageCanvasId = "envelope-image-signature-pad"; -readonly (string Text, string Value)[] TypedSignatureFonts = { + readonly (string Text, string Value)[] TypedSignatureFonts = { ("Brush Script", "'Brush Script MT', cursive"), ("Segoe Script", "'Segoe Script', cursive"), ("Lucida Handwriting", "'Lucida Handwriting', cursive"), @@ -501,47 +544,48 @@ readonly (string Text, string Value)[] TypedSignatureFonts = { ("Cursive", "cursive") }; -[Parameter] public string? EnvelopeKey { get; set; } + [Parameter] public string? EnvelopeKey { get; set; } -bool _isLoading = true; -string? _errorMessage; -string? _pdfDataUrl; -bool _pdfLoaded = false; -int _currentPage = 1; -int _totalPages = 0; -int _currentZoom = 150; -bool _showThumbnails = true; -bool _isLoggingOut = false; -DotNetObjectReference? _dotNetRef; -IReadOnlyList _signatures = []; -EnvelopeReceiverDto? _envelopeReceiver; + bool _isLoading = true; + string? _errorMessage; + string? _pdfDataUrl; + bool _pdfLoaded = false; + int _currentPage = 1; + int _totalPages = 0; + int _currentZoom = 150; + bool _showThumbnails = true; + bool _isLoggingOut = false; + DotNetObjectReference? _dotNetRef; + IReadOnlyList _signatures = []; + EnvelopeReceiverDto? _envelopeReceiver; -// Signature navigation state -int _totalSignatures = 0; -int _signedSignatures = 0; -int _unsignedSignatures = 0; -int _currentSignatureIndex = 0; // Current signature index (1-based) + // Signature navigation state + int _totalSignatures = 0; + int _signedSignatures = 0; + int _unsignedSignatures = 0; + int _currentSignatureIndex = 0; // Current signature index (1-based) -// Signature state -SignatureCaptureDto? _capturedSignature; -bool _signaturePopupVisible = false; -string? _popupValidationMessage; -string _activeSignatureTab = SignatureTabDraw; -string _typedSignatureText = string.Empty; -string _typedSignatureFont = "'Brush Script MT', cursive"; -string _signerFullName = string.Empty; -string _signerPosition = string.Empty; -string _signaturePlace = string.Empty; + // Signature state + SignatureCaptureDto? _capturedSignature; + bool _signaturePopupVisible = false; + string? _popupValidationMessage; + string _activeSignatureTab = SignatureTabDraw; + string _typedSignatureText = string.Empty; + string _typedSignatureFont = "'Brush Script MT', cursive"; + string _signerFullName = string.Empty; + string _signerPosition = string.Empty; + string _signaturePlace = string.Empty; -// Resizable splitter state -int _thumbnailWidth = 260; -bool _isResizing = false; -int _resizeStartX = 0; -int _resizeStartWidth = 0; -const int MinThumbnailWidth = 150; -const int MaxThumbnailWidth = 400; + // Resizable splitter state + int _thumbnailWidth = 260; + bool _isResizing = false; + int _resizeStartX = 0; + int _resizeStartWidth = 0; + const int MinThumbnailWidth = 150; + const int MaxThumbnailWidth = 400; - async Task LogoutAsync() { + async Task LogoutAsync() + { if (string.IsNullOrWhiteSpace(EnvelopeKey) || _isLoggingOut) return; _isLoggingOut = true; await InvokeAsync(StateHasChanged); @@ -549,8 +593,10 @@ const int MaxThumbnailWidth = 400; Navigation.NavigateTo($"/envelope/login/{Uri.EscapeDataString(EnvelopeKey)}", forceLoad: true); } - protected override async Task OnInitializedAsync() { - if (string.IsNullOrWhiteSpace(EnvelopeKey)) { + protected override async Task OnInitializedAsync() + { + if (string.IsNullOrWhiteSpace(EnvelopeKey)) + { _errorMessage = "Envelope-Schlüssel fehlt."; _isLoading = false; return; @@ -558,18 +604,23 @@ const int MaxThumbnailWidth = 400; // Check authentication var hasAccess = await AuthService.CheckEnvelopeAccessAsync(EnvelopeKey); - if (!hasAccess) { + if (!hasAccess) + { Navigation.NavigateTo($"/envelope/login/{Uri.EscapeDataString(EnvelopeKey)}"); return; } - try { + try + { var pdfBytes = await DocumentService.GetDocumentAsync(EnvelopeKey); - if (pdfBytes is { Length: > 0 }) { + if (pdfBytes is { Length: > 0 }) + { var base64 = Convert.ToBase64String(pdfBytes); _pdfDataUrl = $"data:application/pdf;base64,{base64}"; - } else { + } + else + { _errorMessage = "Dokument konnte nicht geladen werden: Keine Daten empfangen."; } @@ -595,7 +646,7 @@ const int MaxThumbnailWidth = 400; _signerPosition = cachedSignature.Position; _signaturePlace = cachedSignature.Place; _signaturePopupVisible = false; - + logger.LogInformation("Cached signature loaded for envelope {EnvelopeKey}", EnvelopeKey); } else @@ -613,10 +664,14 @@ const int MaxThumbnailWidth = 400; _popupValidationMessage = null; } - } catch (HttpRequestException ex) { + } + catch (HttpRequestException ex) + { _errorMessage = $"Dokument konnte nicht geladen werden: {ex.Message}"; logger.LogError(ex, "Failed to load document for envelope {EnvelopeKey}", EnvelopeKey); - } catch (Exception ex) { + } + catch (Exception ex) + { _errorMessage = $"Fehler: {ex.Message}"; logger.LogError(ex, "Unexpected error during initialization for envelope {EnvelopeKey}", EnvelopeKey); } @@ -625,26 +680,34 @@ const int MaxThumbnailWidth = 400; await InvokeAsync(StateHasChanged); } - protected override async Task OnAfterRenderAsync(bool firstRender) { - if (firstRender) { + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender) + { // Load saved thumbnail width from localStorage - try { + try + { var savedWidth = await JSRuntime.InvokeAsync("localStorage.getItem", "envelopeViewer_thumbnailWidth"); - if (!string.IsNullOrEmpty(savedWidth) && int.TryParse(savedWidth, out var width)) { + if (!string.IsNullOrEmpty(savedWidth) && int.TryParse(savedWidth, out var width)) + { _thumbnailWidth = Math.Clamp(width, MinThumbnailWidth, MaxThumbnailWidth); await InvokeAsync(StateHasChanged); } - } catch { + } + catch + { // Ignore localStorage errors } } - if (!_pdfLoaded && !string.IsNullOrWhiteSpace(_pdfDataUrl)) { + if (!_pdfLoaded && !string.IsNullOrWhiteSpace(_pdfDataUrl)) + { await Task.Delay(500); - - try { + + try + { _dotNetRef = DotNetObjectReference.Create(this); - + // Send quality options to JavaScript var options = PdfViewerOptions.Value; await JSRuntime.InvokeVoidAsync("pdfViewer.setQualityOptions", new @@ -659,28 +722,31 @@ const int MaxThumbnailWidth = 400; options.RenderingOpacity, options.ZoomStepPercentage }); - + var success = await JSRuntime.InvokeAsync("pdfViewer.initialize", "pdf-canvas", _pdfDataUrl, _dotNetRef); - - if (success) { + + if (success) + { _pdfLoaded = true; _totalPages = await JSRuntime.InvokeAsync("pdfViewer.getTotalPages"); _currentPage = await JSRuntime.InvokeAsync("pdfViewer.getCurrentPage"); - + // Attach resize listeners await JSRuntime.InvokeVoidAsync("pdfViewer.attachResizeListeners", _dotNetRef); - - + + await InvokeAsync(StateHasChanged); - + // Wait for DOM to be ready, then render thumbnails await Task.Delay(100); await RenderThumbnailsAsync(); - + // Render signature buttons await RenderSignatureButtonsAsync(); } - } catch (Exception ex) { + } + catch (Exception ex) + { _errorMessage = $"PDF.js Fehler: {ex.Message}"; await InvokeAsync(StateHasChanged); } @@ -692,156 +758,188 @@ const int MaxThumbnailWidth = 400; { _currentZoom = (int)(scale * 100); await InvokeAsync(StateHasChanged); - + // Small delay for canvas render to complete (reduced from 100ms to 10ms) await Task.Delay(10); await RenderSignatureButtonsAsync(); } - async Task NextPage() { - if (await JSRuntime.InvokeAsync("pdfViewer.nextPage")) { + async Task NextPage() + { + if (await JSRuntime.InvokeAsync("pdfViewer.nextPage")) + { _currentPage = await JSRuntime.InvokeAsync("pdfViewer.getCurrentPage"); await RenderSignatureButtonsAsync(); } } - async Task PreviousPage() { - if (await JSRuntime.InvokeAsync("pdfViewer.previousPage")) { + async Task PreviousPage() + { + if (await JSRuntime.InvokeAsync("pdfViewer.previousPage")) + { _currentPage = await JSRuntime.InvokeAsync("pdfViewer.getCurrentPage"); await RenderSignatureButtonsAsync(); } } - async Task ZoomIn() { + async Task ZoomIn() + { if (_currentZoom >= 300) return; await JSRuntime.InvokeVoidAsync("pdfViewer.zoomIn"); var scale = await JSRuntime.InvokeAsync("pdfViewer.getScale"); _currentZoom = (int)(scale * 100); - + // Update signature overlay positions after zoom await RenderSignatureButtonsAsync(); } - async Task ZoomOut() { + async Task ZoomOut() + { if (_currentZoom <= 50) return; await JSRuntime.InvokeVoidAsync("pdfViewer.zoomOut"); var scale = await JSRuntime.InvokeAsync("pdfViewer.getScale"); _currentZoom = (int)(scale * 100); - + // Update signature overlay positions after zoom await RenderSignatureButtonsAsync(); } - async Task SetZoom(int percentage) { + async Task SetZoom(int percentage) + { var scale = percentage / 100.0; await JSRuntime.InvokeVoidAsync("pdfViewer.setScale", scale); _currentZoom = percentage; } - async Task OnZoomSliderChanged(ChangeEventArgs e) { - if (int.TryParse(e.Value?.ToString(), out var zoom)) { + async Task OnZoomSliderChanged(ChangeEventArgs e) + { + if (int.TryParse(e.Value?.ToString(), out var zoom)) + { await SetZoom(zoom); - + // Update signature overlay positions after zoom await RenderSignatureButtonsAsync(); } } - async Task OnPageInputChanged(ChangeEventArgs e) { - if (int.TryParse(e.Value?.ToString(), out var pageNum) && pageNum >= 1 && pageNum <= _totalPages) { - if (await JSRuntime.InvokeAsync("pdfViewer.goToPage", pageNum)) { + async Task OnPageInputChanged(ChangeEventArgs e) + { + if (int.TryParse(e.Value?.ToString(), out var pageNum) && pageNum >= 1 && pageNum <= _totalPages) + { + if (await JSRuntime.InvokeAsync("pdfViewer.goToPage", pageNum)) + { _currentPage = pageNum; } } } - async Task FitToWidth() { + async Task FitToWidth() + { await JSRuntime.InvokeVoidAsync("pdfViewer.fitToWidth"); var scale = await JSRuntime.InvokeAsync("pdfViewer.getScale"); _currentZoom = (int)(scale * 100); } - async Task ToggleThumbnails() { + async Task ToggleThumbnails() + { _showThumbnails = !_showThumbnails; - + // Re-render thumbnails when showing them - if (_showThumbnails && _pdfLoaded) { + if (_showThumbnails && _pdfLoaded) + { await InvokeAsync(StateHasChanged); // Force UI update first await Task.Delay(150); // Wait for DOM to render canvas elements await RenderThumbnailsAsync(); } } - async Task GoToPageFromThumbnail(int pageNum) { - if (await JSRuntime.InvokeAsync("pdfViewer.goToPage", pageNum)) { + async Task GoToPageFromThumbnail(int pageNum) + { + if (await JSRuntime.InvokeAsync("pdfViewer.goToPage", pageNum)) + { _currentPage = pageNum; await RenderSignatureButtonsAsync(); } } - async Task RenderSignatureButtonsAsync() { + async Task RenderSignatureButtonsAsync() + { if (_signatures.Count == 0 || !_pdfLoaded) return; - try { + try + { await JSRuntime.InvokeVoidAsync("pdfViewer.renderSignatureButtons", _signatures, _currentPage, _dotNetRef); await UpdateSignatureCounterAsync(); - } catch (Exception ex) { + } + catch (Exception ex) + { System.Diagnostics.Debug.WriteLine($"Signature button rendering error: {ex.Message}"); } } [JSInvokable] - public async Task OnSignatureButtonClick(int signatureId) { - if (_capturedSignature == null) { + public async Task OnSignatureButtonClick(int signatureId) + { + if (_capturedSignature == null) + { // No signature captured yet - should not happen as popup is shown on page load return; } // Apply signature to PDF canvas - await JSRuntime.InvokeVoidAsync("pdfViewer.applySignature", - signatureId, + await JSRuntime.InvokeVoidAsync("pdfViewer.applySignature", + signatureId, _capturedSignature.DataUrl, _capturedSignature.FullName, _capturedSignature.Position, _capturedSignature.Place); - + // Update counter await UpdateSignatureCounterAsync(); } [JSInvokable] - public async Task OnSignatureNavChanged() { + public async Task OnSignatureNavChanged() + { await UpdateSignatureCounterAsync(); } [JSInvokable] - public async Task OnPageChangedBySignatureNav(int newPage) { + public async Task OnPageChangedBySignatureNav(int newPage) + { _currentPage = newPage; await RenderSignatureButtonsAsync(); } - async Task UpdateSignatureCounterAsync() { - try { + async Task UpdateSignatureCounterAsync() + { + try + { var state = await JSRuntime.InvokeAsync("pdfViewer.getSignatureNavState"); _totalSignatures = state.Total; _signedSignatures = state.Signed; _unsignedSignatures = state.Unsigned; _currentSignatureIndex = state.CurrentIndex; // Current signature await InvokeAsync(StateHasChanged); - } catch { + } + catch + { // Ignore errors during counter update } } - async Task GoToPreviousSignature() { + async Task GoToPreviousSignature() + { await JSRuntime.InvokeVoidAsync("pdfViewer.goToPreviousSignature", _dotNetRef); } - async Task GoToNextSignature() { + async Task GoToNextSignature() + { await JSRuntime.InvokeVoidAsync("pdfViewer.goToNextSignature", _dotNetRef); } - void RestartSigning() { + void RestartSigning() + { // Force page reload to reset all signatures and state Navigation.NavigateTo(Navigation.Uri, forceLoad: true); } @@ -852,9 +950,9 @@ const int MaxThumbnailWidth = 400; { if (_signedSignatures > 0) return "Unterschrift ist gesperrt – bitte Seite neu laden, um zu ändern"; - - return _capturedSignature is not null - ? "Unterschrift ändern" + + return _capturedSignature is not null + ? "Unterschrift ändern" : "Unterschrift erstellen"; } @@ -864,18 +962,19 @@ const int MaxThumbnailWidth = 400; // But just in case, do nothing if (_signedSignatures > 0) return; - + // No signatures applied - open popup normally OpenSignaturePopup(); } // Signature popup methods - void OpenSignaturePopup() { + void OpenSignaturePopup() + { // Open popup with current signature (edit mode) _activeSignatureTab = SignatureTabDraw; _signaturePopupVisible = true; _popupValidationMessage = null; - + // Load current signature info into form fields if (_capturedSignature is not null) { @@ -885,9 +984,10 @@ const int MaxThumbnailWidth = 400; } } - async Task OnPopupShownAsync() { + async Task OnPopupShownAsync() + { await InitializeActiveSignatureTabAsync(); - + // If there's an existing signature and we're on draw tab, load it to canvas if (_capturedSignature is not null && _activeSignatureTab == SignatureTabDraw) { @@ -896,7 +996,8 @@ const int MaxThumbnailWidth = 400; } } - async Task SetSignatureTabAsync(string tab) { + async Task SetSignatureTabAsync(string tab) + { _activeSignatureTab = tab; _popupValidationMessage = null; await InvokeAsync(StateHasChanged); @@ -904,55 +1005,74 @@ const int MaxThumbnailWidth = 400; await InitializeActiveSignatureTabAsync(); } - async Task InitializeActiveSignatureTabAsync() { - if(_activeSignatureTab == SignatureTabDraw) { + async Task InitializeActiveSignatureTabAsync() + { + if (_activeSignatureTab == SignatureTabDraw) + { await JSRuntime.InvokeVoidAsync("receiverSignature.initialize", DrawCanvasId); - } else if(_activeSignatureTab == SignatureTabText) { + } + else if (_activeSignatureTab == SignatureTabText) + { await JSRuntime.InvokeVoidAsync("receiverSignature.initializeTyped", TypedCanvasId); await RenderTypedSignatureAsync(); - } else { + } + else + { await JSRuntime.InvokeVoidAsync("receiverSignature.initializeImage", ImageInputId, ImageCanvasId); } } - async Task RenewSignatureAsync() { + async Task RenewSignatureAsync() + { _popupValidationMessage = null; - if(_activeSignatureTab == SignatureTabDraw) { + if (_activeSignatureTab == SignatureTabDraw) + { await JSRuntime.InvokeVoidAsync("receiverSignature.clear", DrawCanvasId); - } else if(_activeSignatureTab == SignatureTabText) { + } + else if (_activeSignatureTab == SignatureTabText) + { _typedSignatureText = string.Empty; await JSRuntime.InvokeVoidAsync("receiverSignature.clearTyped", TypedCanvasId); - } else { + } + else + { await JSRuntime.InvokeVoidAsync("receiverSignature.clearImage", ImageInputId, ImageCanvasId); } } - async Task OnTypedSignatureChanged(Microsoft.AspNetCore.Components.ChangeEventArgs args) { + async Task OnTypedSignatureChanged(Microsoft.AspNetCore.Components.ChangeEventArgs args) + { _typedSignatureText = args.Value?.ToString() ?? string.Empty; await RenderTypedSignatureAsync(); } - async Task OnTypedSignatureFontChanged(Microsoft.AspNetCore.Components.ChangeEventArgs args) { + async Task OnTypedSignatureFontChanged(Microsoft.AspNetCore.Components.ChangeEventArgs args) + { _typedSignatureFont = args.Value?.ToString() ?? _typedSignatureFont; await RenderTypedSignatureAsync(); } - async Task RenderTypedSignatureAsync() { + async Task RenderTypedSignatureAsync() + { await JSRuntime.InvokeVoidAsync("receiverSignature.renderTypedSignature", TypedCanvasId, _typedSignatureText, _typedSignatureFont); } - async Task SaveSignatureAsync() { - if (string.IsNullOrWhiteSpace(_signerFullName)) { + async Task SaveSignatureAsync() + { + if (string.IsNullOrWhiteSpace(_signerFullName)) + { _popupValidationMessage = "Bitte geben Sie Vor- und Nachname ein."; return; } - if (string.IsNullOrWhiteSpace(_signaturePlace)) { + if (string.IsNullOrWhiteSpace(_signaturePlace)) + { _popupValidationMessage = "Bitte geben Sie den Ort ein."; return; } var signatureDataUrl = await GetActiveSignatureDataUrlAsync(); - if (string.IsNullOrWhiteSpace(signatureDataUrl)) { + if (string.IsNullOrWhiteSpace(signatureDataUrl)) + { _popupValidationMessage = "Die Unterschrift ist erforderlich."; return; } @@ -966,7 +1086,7 @@ const int MaxThumbnailWidth = 400; Place = _signaturePlace.Trim() }; _signaturePopupVisible = false; - + // Save to cache (fire-and-forget, ignore errors) if (!string.IsNullOrWhiteSpace(EnvelopeKey)) { @@ -982,16 +1102,18 @@ const int MaxThumbnailWidth = 400; } }); } - + await InvokeAsync(StateHasChanged); Console.WriteLine($"Signature saved: {_signerFullName}, {_signaturePlace}"); } - async Task GetActiveSignatureDataUrlAsync() { - if(_activeSignatureTab == SignatureTabDraw) + async Task GetActiveSignatureDataUrlAsync() + { + if (_activeSignatureTab == SignatureTabDraw) return await JSRuntime.InvokeAsync("receiverSignature.getDataUrl", DrawCanvasId); - if(_activeSignatureTab == SignatureTabText) { + if (_activeSignatureTab == SignatureTabText) + { await RenderTypedSignatureAsync(); return await JSRuntime.InvokeAsync("receiverSignature.getTypedDataUrl", TypedCanvasId); } @@ -999,69 +1121,83 @@ const int MaxThumbnailWidth = 400; return await JSRuntime.InvokeAsync("receiverSignature.getImageDataUrl", ImageCanvasId); } - async Task RenderThumbnailsAsync() { - try { + async Task RenderThumbnailsAsync() + { + try + { var delay = PdfViewerOptions.Value.ThumbnailRenderDelay; - + // Sequential rendering to avoid overwhelming the browser - for (int i = 1; i <= _totalPages; i++) { + for (int i = 1; i <= _totalPages; i++) + { await JSRuntime.InvokeVoidAsync("pdfViewer.renderThumbnail", i, $"thumb-canvas-{i}"); - + // Configurable delay between renders - if (i < _totalPages) { + if (i < _totalPages) + { await Task.Delay(delay); } } - } catch (Exception ex) { + } + catch (Exception ex) + { // Thumbnail rendering is not critical System.Diagnostics.Debug.WriteLine($"Thumbnail rendering error: {ex.Message}"); } } // Resizable splitter methods - void OnSplitterMouseDown(MouseEventArgs e) { + void OnSplitterMouseDown(MouseEventArgs e) + { _isResizing = true; _resizeStartX = (int)e.ClientX; _resizeStartWidth = _thumbnailWidth; - + // Add resizing class to body to prevent text selection _ = JSRuntime.InvokeVoidAsync("eval", "document.body.classList.add('resizing')"); _ = JSRuntime.InvokeVoidAsync("pdfViewer.startResize"); } [JSInvokable] - public async Task OnSplitterMouseMove(int clientX) { + public async Task OnSplitterMouseMove(int clientX) + { if (!_isResizing) return; var delta = clientX - _resizeStartX; var newWidth = _resizeStartWidth + delta; - + // Clamp to min/max _thumbnailWidth = Math.Clamp(newWidth, MinThumbnailWidth, MaxThumbnailWidth); - + await InvokeAsync(StateHasChanged); } [JSInvokable] - public async Task OnSplitterMouseUp() { + public async Task OnSplitterMouseUp() + { if (!_isResizing) return; - + _isResizing = false; - + // Remove resizing class from body await JSRuntime.InvokeVoidAsync("eval", "document.body.classList.remove('resizing')"); - + // Save preference to localStorage await JSRuntime.InvokeVoidAsync("localStorage.setItem", "envelopeViewer_thumbnailWidth", _thumbnailWidth.ToString()); - + await InvokeAsync(StateHasChanged); } - public async ValueTask DisposeAsync() { - if (_pdfLoaded) { - try { + public async ValueTask DisposeAsync() + { + if (_pdfLoaded) + { + try + { await JSRuntime.InvokeVoidAsync("pdfViewer.dispose"); - } catch { + } + catch + { // Ignore errors during disposal } } diff --git a/EnvelopeGenerator.Server/EnvelopeGenerator.Server/Components/Pages/EnvelopeReceiverPage_DxPdfViewer.razor b/EnvelopeGenerator.Server/EnvelopeGenerator.Server/Components/Pages/EnvelopeReceiverPage_DxPdfViewer.razor index 5070e918..a9a3d66f 100644 --- a/EnvelopeGenerator.Server/EnvelopeGenerator.Server/Components/Pages/EnvelopeReceiverPage_DxPdfViewer.razor +++ b/EnvelopeGenerator.Server/EnvelopeGenerator.Server/Components/Pages/EnvelopeReceiverPage_DxPdfViewer.razor @@ -1,6 +1,7 @@ @page "/envelope/DxPdfViewer" @rendermode InteractiveServer @using System.IO +@using DevExpress.Blazor @using System.Reflection @using DevExpress.Blazor.PdfViewer