Add document preview feature to wizard popup

Introduced a document preview feature in the `CreateEnvelopeWizardPopup` component. This includes:

- Injected `PdfRenderService` for rendering PDF pages.
- Replaced document metadata display with a preview card.
- Added `SenderPdfImageEditor` for interactive PDF previews.
- Implemented state management for preview pages and overlays.
- Added fallback logic to estimate PDF page count.
- Enhanced `ResetWizardState` to reset preview-related fields.
- Styled the preview card and wrap with new CSS classes.
- Added placeholder event handlers for editor interactions.
This commit is contained in:
2026-10-06 11:48:03 +02:00
parent 47f48ba12a
commit de9d8c6f37
2 changed files with 110 additions and 3 deletions

View File

@@ -9,6 +9,7 @@
@inject IStringLocalizer<Model> ModelLocalizer
@inject IStringLocalizer<Resource> UiLocalizer
@inject ReceiverService ReceiverService
@inject PdfRenderService PdfRenderService
<DxPopup Visible="@_isVisible"
VisibleChanged="OnPopupVisibleChangedAsync"
@@ -238,9 +239,22 @@
@if (_documentBytes is not null)
{
<div class="sender-create-wizard__document-meta">
<div class="sender-create-wizard__document-name">@_documentFileName</div>
<div class="sender-create-wizard__document-size">@_documentSizeText</div>
<div class="sender-create-wizard__document-preview-card">
<div class="sender-create-wizard__document-meta">
<div class="sender-create-wizard__document-name">@_documentFileName</div>
<div class="sender-create-wizard__document-size">@_documentSizeText</div>
</div>
<div class="sender-create-wizard__document-preview-wrap">
<SenderPdfImageEditor IsPlacementMode="false"
PageCount="@_documentPreviewPageCount"
Pages="@_documentPreviewPages"
PdfDataUrl="@DocumentPreviewDataUrl"
PdfFileName="@_documentFileName"
Fields="@_documentPreviewFieldOverlays"
EditorClick="OnDocumentPreviewEditorClickAsync"
RemoveField="OnDocumentPreviewRemoveFieldAsync"
MoveField="OnDocumentPreviewMoveFieldAsync" />
</div>
</div>
}
else
@@ -331,10 +345,14 @@
private byte[]? _documentBytes;
private string? _documentFileName;
private string _documentSizeText = string.Empty;
private int _documentPreviewPageCount;
private List<PdfPageImageDto> _documentPreviewPages = [];
private bool _isDocumentUploading;
private bool _isStepActionRunning;
private bool _isFinishing;
private readonly IReadOnlyList<SenderPdfFieldOverlay> _documentPreviewFieldOverlays = [];
private string? _validationMessage;
private bool IsActionLocked => _isDocumentUploading || _isStepActionRunning || _isFinishing;
@@ -346,6 +364,10 @@
private IReadOnlyList<RecentReceiverSuggestionDto> FilteredRecentSuggestions =>
BuildFilteredRecentSuggestions();
private string? DocumentPreviewDataUrl => _documentBytes is { Length: > 0 }
? $"data:application/pdf;base64,{Convert.ToBase64String(_documentBytes)}"
: null;
private string CurrentStepTitle => _currentStep switch
{
0 => L("SenderWizard_Step_Basics", "Grunddaten"),
@@ -525,6 +547,22 @@
_documentBytes = ms.ToArray();
_documentFileName = file.Name;
_documentSizeText = FormatSize(_documentBytes.Length);
_documentPreviewPages = [];
try
{
_documentPreviewPages = (await PdfRenderService.RenderPagesAsync(_documentBytes))
.OrderBy(x => x.PageNumber)
.ToList();
}
catch
{
_documentPreviewPages = [];
}
_documentPreviewPageCount = _documentPreviewPages.Count > 0
? _documentPreviewPages.Count
: EstimatePdfPageCount(_documentBytes) ?? 1;
}
finally
{
@@ -771,6 +809,51 @@
return $"{bytes} B";
}
private 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;
}
}
private Task OnDocumentPreviewEditorClickAsync(MouseEventArgs _)
{
return Task.CompletedTask;
}
private Task OnDocumentPreviewRemoveFieldAsync(int _)
{
return Task.CompletedTask;
}
private Task OnDocumentPreviewMoveFieldAsync(SenderPdfFieldMoveRequest _)
{
return Task.CompletedTask;
}
private void ResetWizardState()
{
_currentStep = 0;
@@ -792,6 +875,8 @@
_documentBytes = null;
_documentFileName = null;
_documentSizeText = string.Empty;
_documentPreviewPageCount = 0;
_documentPreviewPages = [];
}
private int? GetDefaultEnvelopeTypeId()

View File

@@ -1533,6 +1533,28 @@
padding: 0.55rem 0.7rem;
}
.sender-create-wizard__document-preview-card {
display: flex;
flex-direction: column;
gap: 0.65rem;
}
.sender-create-wizard__document-preview-wrap {
border: 1px solid rgba(148, 163, 184, 0.38);
border-radius: 10px;
background: rgba(248, 250, 252, 0.96);
height: 320px;
overflow: hidden;
}
.sender-create-wizard__document-preview-wrap .sender-image-editor-shell {
height: 100%;
}
.sender-create-wizard__document-preview-wrap .sender-image-editor {
min-height: 0;
}
.sender-create-wizard__document-name {
color: #064e3b;
font-size: 0.85rem;