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<Model> ModelLocalizer
@inject IStringLocalizer<Resource> UiLocalizer @inject IStringLocalizer<Resource> UiLocalizer
@inject ReceiverService ReceiverService @inject ReceiverService ReceiverService
@inject PdfRenderService PdfRenderService
<DxPopup Visible="@_isVisible" <DxPopup Visible="@_isVisible"
VisibleChanged="OnPopupVisibleChangedAsync" VisibleChanged="OnPopupVisibleChangedAsync"
@@ -238,10 +239,23 @@
@if (_documentBytes is not null) @if (_documentBytes is not null)
{ {
<div class="sender-create-wizard__document-preview-card">
<div class="sender-create-wizard__document-meta"> <div class="sender-create-wizard__document-meta">
<div class="sender-create-wizard__document-name">@_documentFileName</div> <div class="sender-create-wizard__document-name">@_documentFileName</div>
<div class="sender-create-wizard__document-size">@_documentSizeText</div> <div class="sender-create-wizard__document-size">@_documentSizeText</div>
</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 else
{ {
@@ -331,10 +345,14 @@
private byte[]? _documentBytes; private byte[]? _documentBytes;
private string? _documentFileName; private string? _documentFileName;
private string _documentSizeText = string.Empty; private string _documentSizeText = string.Empty;
private int _documentPreviewPageCount;
private List<PdfPageImageDto> _documentPreviewPages = [];
private bool _isDocumentUploading; private bool _isDocumentUploading;
private bool _isStepActionRunning; private bool _isStepActionRunning;
private bool _isFinishing; private bool _isFinishing;
private readonly IReadOnlyList<SenderPdfFieldOverlay> _documentPreviewFieldOverlays = [];
private string? _validationMessage; private string? _validationMessage;
private bool IsActionLocked => _isDocumentUploading || _isStepActionRunning || _isFinishing; private bool IsActionLocked => _isDocumentUploading || _isStepActionRunning || _isFinishing;
@@ -346,6 +364,10 @@
private IReadOnlyList<RecentReceiverSuggestionDto> FilteredRecentSuggestions => private IReadOnlyList<RecentReceiverSuggestionDto> FilteredRecentSuggestions =>
BuildFilteredRecentSuggestions(); BuildFilteredRecentSuggestions();
private string? DocumentPreviewDataUrl => _documentBytes is { Length: > 0 }
? $"data:application/pdf;base64,{Convert.ToBase64String(_documentBytes)}"
: null;
private string CurrentStepTitle => _currentStep switch private string CurrentStepTitle => _currentStep switch
{ {
0 => L("SenderWizard_Step_Basics", "Grunddaten"), 0 => L("SenderWizard_Step_Basics", "Grunddaten"),
@@ -525,6 +547,22 @@
_documentBytes = ms.ToArray(); _documentBytes = ms.ToArray();
_documentFileName = file.Name; _documentFileName = file.Name;
_documentSizeText = FormatSize(_documentBytes.Length); _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 finally
{ {
@@ -771,6 +809,51 @@
return $"{bytes} B"; 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() private void ResetWizardState()
{ {
_currentStep = 0; _currentStep = 0;
@@ -792,6 +875,8 @@
_documentBytes = null; _documentBytes = null;
_documentFileName = null; _documentFileName = null;
_documentSizeText = string.Empty; _documentSizeText = string.Empty;
_documentPreviewPageCount = 0;
_documentPreviewPages = [];
} }
private int? GetDefaultEnvelopeTypeId() private int? GetDefaultEnvelopeTypeId()

View File

@@ -1533,6 +1533,28 @@
padding: 0.55rem 0.7rem; 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 { .sender-create-wizard__document-name {
color: #064e3b; color: #064e3b;
font-size: 0.85rem; font-size: 0.85rem;