Enhance PDF upload UX with progress and state handling

Refactor the PDF upload button to dynamically adjust its
appearance and behavior based on `_isPageReady` and
`_isPdfUploadInProgress` states. Add a spinner to indicate
upload progress and disable the `<InputFile>` component
during uploads or when the page is not ready.

Introduce `_isPdfUploadInProgress` state to prevent multiple
uploads and ensure consistent UI updates. Update the
`OnPdfFileSelectedAsync` method to handle this state,
including a `finally` block to reset it and trigger UI
updates.

Consolidate redundant code for disabled states and improve
conditional rendering for better maintainability. Enhance
visual feedback for loading states and make minor style
adjustments for consistency.
This commit is contained in:
2026-09-29 22:18:25 +02:00
parent 2434f67627
commit 696cd1b623

View File

@@ -159,29 +159,43 @@
</button> </button>
@* PDF Upload *@ @* PDF Upload *@
@if (_isPageReady) <label class="pdf-toolbar__btn"
{ title='@L("UploadPdf", "PDF hochladen")'
<label class="pdf-toolbar__btn" style="cursor: @((_isPageReady && !_isPdfUploadInProgress) ? "pointer" : "not-allowed");
title='@L("UploadPdf", "PDF hochladen")' opacity:@((_isPageReady && !_isPdfUploadInProgress) ? "1" : "0.6");
style="cursor: pointer; display: inline-flex; align-items: center; gap: 0.3rem; padding: 0.3rem 0.75rem; font-size: 0.75rem; font-weight: 500;"> display: inline-flex; align-items: center; gap: 0.3rem; padding: 0.3rem 0.75rem; font-size: 0.75rem; font-weight: 500;">
@if (_isPdfUploadInProgress)
{
<span class="spinner-border spinner-border-sm" role="status" style="width:0.8rem;height:0.8rem;border-width:2px;"></span>
}
else
{
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="currentColor" viewBox="0 0 16 16"> <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="currentColor" viewBox="0 0 16 16">
<path d="M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5z" /> <path d="M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5z" />
<path d="M7.646 1.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1-.708.708L8.5 2.707V11.5a.5.5 0 0 1-1 0V2.707L5.354 4.854a.5.5 0 1 1-.708-.708l3-3z" /> <path d="M7.646 1.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1-.708.708L8.5 2.707V11.5a.5.5 0 0 1-1 0V2.707L5.354 4.854a.5.5 0 1 1-.708-.708l3-3z" />
</svg> </svg>
}
@if (_isPdfUploadInProgress)
{
@L("UploadPdf", "PDF hochladen") @L("UploadPdf", "PDF hochladen")
<InputFile OnChange="OnPdfFileSelectedAsync" @:
accept=".pdf" @:...
style="display: none;" /> }
</label> else if (_isPageReady)
} {
else @L("UploadPdf", "PDF hochladen")
{ }
<span class="pdf-toolbar__btn" else
style="opacity:0.6; cursor:not-allowed; display: inline-flex; align-items: center; gap: 0.3rem; padding: 0.3rem 0.75rem; font-size: 0.75rem; font-weight: 500;"> {
<span class="spinner-border spinner-border-sm" role="status" style="width:0.8rem;height:0.8rem;border-width:2px;"></span> @L("Loading", "Wird geladen")
@L("Loading", "Wird geladen")… @:
</span> @:...
} }
<InputFile OnChange="OnPdfFileSelectedAsync"
accept=".pdf"
disabled="@(!_isPageReady || _isPdfUploadInProgress)"
style="display: none;" />
</label>
@if (_pdfLoaded && _signatureFields.Count > 0) @if (_pdfLoaded && _signatureFields.Count > 0)
{ {
@@ -267,24 +281,42 @@
<p class="text-white mb-4" style="opacity: 0.75; font-size: 0.85rem;"> <p class="text-white mb-4" style="opacity: 0.75; font-size: 0.85rem;">
@L("SenderEditor_UploadPdfToPlaceFields", "Laden Sie eine PDF-Datei hoch, um Signaturfelder zu platzieren.") @L("SenderEditor_UploadPdfToPlaceFields", "Laden Sie eine PDF-Datei hoch, um Signaturfelder zu platzieren.")
</p> </p>
@if (_isPageReady) <label class="btn btn-light btn-sm"
{ style="cursor: @((_isPageReady && !_isPdfUploadInProgress) ? "pointer" : "not-allowed");
<label class="btn btn-light btn-sm" style="cursor: pointer; font-weight: 500;"> opacity:@((_isPageReady && !_isPdfUploadInProgress) ? "1" : "0.8");
font-weight: 500;">
@if (_isPdfUploadInProgress)
{
<span class="spinner-border spinner-border-sm me-1" role="status" style="width:0.8rem;height:0.8rem;border-width:2px;"></span>
}
else
{
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="currentColor" class="me-1" viewBox="0 0 16 16"> <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="currentColor" class="me-1" viewBox="0 0 16 16">
<path d="M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5z" /> <path d="M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5z" />
<path d="M7.646 1.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1-.708.708L8.5 2.707V11.5a.5.5 0 0 1-1 0V2.707L5.354 4.854a.5.5 0 1 1-.708-.708l3-3z" /> <path d="M7.646 1.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1-.708.708L8.5 2.707V11.5a.5.5 0 0 1-1 0V2.707L5.354 4.854a.5.5 0 1 1-.708-.708l3-3z" />
</svg> </svg>
}
@if (_isPdfUploadInProgress)
{
@L("UploadPdf", "PDF hochladen") @L("UploadPdf", "PDF hochladen")
<InputFile OnChange="OnPdfFileSelectedAsync" accept=".pdf" style="display: none;" /> @:
</label> @:...
} }
else else if (_isPageReady)
{ {
<button class="btn btn-light btn-sm" type="button" disabled> @L("UploadPdf", "PDF hochladen")
<span class="spinner-border spinner-border-sm me-1" role="status" style="width:0.8rem;height:0.8rem;border-width:2px;"></span> }
@L("Loading", "Wird geladen")… else
</button> {
} @L("Loading", "Wird geladen")
@:
@:...
}
<InputFile OnChange="OnPdfFileSelectedAsync"
accept=".pdf"
disabled="@(!_isPageReady || _isPdfUploadInProgress)"
style="display: none;" />
</label>
</div> </div>
</div> </div>
} }
@@ -490,6 +522,7 @@
// ── State ── // ── State ──
bool _pdfLoaded = false; bool _pdfLoaded = false;
bool _isPageReady; bool _isPageReady;
bool _isPdfUploadInProgress;
string _fileName = string.Empty; string _fileName = string.Empty;
string? _errorMessage; string? _errorMessage;
byte[]? _originalPdfBytes; // Pristine upload — never modified, used as base for redraw byte[]? _originalPdfBytes; // Pristine upload — never modified, used as base for redraw
@@ -658,6 +691,9 @@
if (!_isPageReady) if (!_isPageReady)
return; return;
if (_isPdfUploadInProgress)
return;
_errorMessage = null; _errorMessage = null;
var file = e.File; var file = e.File;
if (file is null) return; if (file is null) return;
@@ -670,6 +706,9 @@
try try
{ {
_isPdfUploadInProgress = true;
await InvokeAsync(StateHasChanged);
const long maxBytes = 50 * 1024 * 1024; const long maxBytes = 50 * 1024 * 1024;
using var ms = new System.IO.MemoryStream(); using var ms = new System.IO.MemoryStream();
await file.OpenReadStream(maxBytes).CopyToAsync(ms); await file.OpenReadStream(maxBytes).CopyToAsync(ms);
@@ -695,6 +734,11 @@
ex.Message); ex.Message);
Logger.LogError(ex, "Failed to load PDF file"); Logger.LogError(ex, "Failed to load PDF file");
} }
finally
{
_isPdfUploadInProgress = false;
await InvokeAsync(StateHasChanged);
}
} }
// ── Placement mode ── // ── Placement mode ──