diff --git a/EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Pages/EnvelopeSenderPage.razor b/EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Pages/EnvelopeSenderPage.razor index b56dc33a..fdfbd7e0 100644 --- a/EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Pages/EnvelopeSenderPage.razor +++ b/EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Pages/EnvelopeSenderPage.razor @@ -4,7 +4,6 @@ @using EnvelopeGenerator.Domain.Constants @using EnvelopeGenerator.Server.Client.Models @using DevExpress.Blazor -@using DevExpress.Blazor.PdfViewer @using EnvelopeGenerator.Server.Client.Services @using EnvelopeGenerator.Application.Common.Dto @using EnvelopeGenerator.Application.Common.Dto.History @@ -13,6 +12,7 @@ @using Microsoft.Extensions.Localization @inject EnvelopeGenerator.Server.Client.Services.EnvelopeService EnvelopeService @inject EnvelopeGenerator.Server.Client.Services.DocumentService DocumentService +@inject EnvelopeGenerator.Server.Client.Services.PdfRenderService PdfRenderService @inject EnvelopeGenerator.Server.Client.Services.AuthService AuthService @inject EnvelopeGenerator.Server.Client.Services.ThemeService ThemeService @inject EnvelopeGenerator.Server.Client.Services.UserPreferencesService UserPreferencesService @@ -465,10 +465,15 @@ else if (_previewPdfBytes is not null && _previewViewerMounted) {
- +
} @@ -542,7 +547,15 @@ private bool _previewLoading = false; private bool _previewViewerMounted = false; private byte[]? _previewPdfBytes = null; + private int _previewPageCount = 0; + private List _previewPagePreviews = []; + private string _previewFileName = "document.pdf"; private string? _previewErrorMessage = null; + private readonly IReadOnlyList _previewFieldOverlays = []; + + private string? PreviewPdfDataUrl => _previewPdfBytes is { Length: > 0 } + ? $"data:application/pdf;base64,{Convert.ToBase64String(_previewPdfBytes)}" + : null; // Phase 6e (step 1): UI state only. Export logic is added in step 2. private string _selectedExportFormat = "xlsx"; @@ -960,6 +973,9 @@ // Unmount the viewer first — let the previous instance fully dispose _previewViewerMounted = false; _previewPdfBytes = null; + _previewPageCount = 0; + _previewPagePreviews = []; + _previewFileName = $"envelope-{envelope.Id}.pdf"; _previewErrorMessage = null; _previewLoading = true; _previewVisible = true; @@ -968,6 +984,29 @@ try { _previewPdfBytes = await DocumentService.GetSenderDocumentBytesByEnvelopeIdAsync(envelope.Id); + if (_previewPdfBytes is null || _previewPdfBytes.Length == 0) + { + _previewErrorMessage = L("DocumentCouldNotBeLoaded", "Dokument konnte nicht geladen werden"); + return; + } + + var previewPdfBytes = _previewPdfBytes; + + _previewPagePreviews = []; + try + { + _previewPagePreviews = (await PdfRenderService.RenderPagesAsync(previewPdfBytes)) + .OrderBy(p => p.PageNumber) + .ToList(); + } + catch (Exception renderEx) + { + Logger.LogWarning(renderEx, "Preview: page image rendering failed for envelope {EnvelopeId}. Falling back to native PDF preview.", envelope.Id); + } + + _previewPageCount = _previewPagePreviews.Count > 0 + ? _previewPagePreviews.Count + : EstimatePdfPageCount(previewPdfBytes) ?? 1; // PDF ready — mount the viewer now _previewViewerMounted = true; @@ -1002,12 +1041,60 @@ { await Task.Yield(); // wait one render cycle for the viewer to be removed from the DOM _previewPdfBytes = null; + _previewPageCount = 0; + _previewPagePreviews = []; + _previewFileName = "document.pdf"; _previewErrorMessage = null; _previewLoading = false; await InvokeAsync(StateHasChanged); }); } + Task OnPreviewEditorClickAsync(Microsoft.AspNetCore.Components.Web.MouseEventArgs _) + { + return Task.CompletedTask; + } + + Task OnPreviewRemoveFieldAsync(int _) + { + return Task.CompletedTask; + } + + Task OnPreviewMoveFieldAsync(SenderPdfFieldMoveRequest _) + { + return Task.CompletedTask; + } + + static int? EstimatePdfPageCount(byte[]? documentBytes) + { + if (documentBytes is null || documentBytes.Length == 0) + return null; + + try + { + var text = System.Text.Encoding.ASCII.GetString(documentBytes); + const string marker = "/Type /Page"; + var count = 0; + var index = 0; + + while (true) + { + index = text.IndexOf(marker, index, StringComparison.Ordinal); + if (index < 0) + break; + + count++; + index += marker.Length; + } + + return count > 0 ? count : null; + } + catch + { + return null; + } + } + // ── Grid Layout Persistence ─────────────────────────────────────────────── // LayoutAutoLoading fires when the grid initializes — restore saved layout. // LayoutAutoSaving fires when the user changes column order/width/sort — persist it.