Enhance signature popup with multiple input modes

Refactored `@using` directives in `ReportViewer.razor` to use specific aliases for improved readability. Updated the signature popup to support three input modes: "Draw", "Text", and "Image", with a tabbed interface for switching between modes. Added font selection for typed signatures and improved state management for signature modes.

Refactored JavaScript interop methods to handle initialization, clearing, and data retrieval for each signature mode. Enhanced `receiver-signature.js` to support typed and image-based signatures, including rendering text-based signatures and handling image uploads with scaling and centering.

Improved maintainability by modularizing code and extracting reusable functions. Enhanced user experience with dynamic UI updates and better handling of signature state changes.
This commit is contained in:
2026-05-28 14:39:38 +02:00
parent 0ca487d5bd
commit 0a22e4e5cc
2 changed files with 257 additions and 10 deletions

View File

@@ -4,7 +4,12 @@
@using DevExpress.Utils
@using DevExpress.XtraPrinting
@using DevExpress.XtraPrinting.Drawing
@using DevExpress.XtraReports.UI;
@using XtraReport = DevExpress.XtraReports.UI.XtraReport
@using BottomMarginBand = DevExpress.XtraReports.UI.BottomMarginBand
@using XRLabel = DevExpress.XtraReports.UI.XRLabel
@using XRPictureBox = DevExpress.XtraReports.UI.XRPictureBox
@using XRControl = DevExpress.XtraReports.UI.XRControl
@using ImageSizeMode = DevExpress.XtraPrinting.ImageSizeMode
@using EnvelopeGenerator.ReceiverUI.Services;
@inject IJSRuntime JSRuntime
@inject InMemoryReportStorageWebExtension ReportStorage
@@ -36,13 +41,47 @@
<DxPopup @bind-Visible="SignaturePopupVisible"
HeaderText="Unterschrift erfassen"
Width="520px"
Width="620px"
ShowFooter="true"
CloseOnEscape="true"
CloseOnOutsideClick="false">
<BodyContentTemplate>
<p class="text-muted mb-2">Bitte unterschreiben Sie im folgenden Feld.</p>
<canvas id="receiver-signature-pad" width="420" height="150" class="border rounded bg-white w-100" style="max-width: 420px; touch-action: none;"></canvas>
<ul class="nav nav-tabs mb-3">
<li class="nav-item">
<button type="button" class="nav-link @(ActiveSignatureTab == SignatureTabDraw ? "active" : null)" @onclick="() => SetSignatureTabAsync(SignatureTabDraw)">Zeichnen</button>
</li>
<li class="nav-item">
<button type="button" class="nav-link @(ActiveSignatureTab == SignatureTabText ? "active" : null)" @onclick="() => SetSignatureTabAsync(SignatureTabText)">Text</button>
</li>
<li class="nav-item">
<button type="button" class="nav-link @(ActiveSignatureTab == SignatureTabImage ? "active" : null)" @onclick="() => SetSignatureTabAsync(SignatureTabImage)">Bild</button>
</li>
</ul>
@if(ActiveSignatureTab == SignatureTabDraw) {
<p class="text-muted mb-2">Bitte unterschreiben Sie im folgenden Feld.</p>
<canvas id="receiver-signature-pad" width="520" height="160" class="border rounded bg-white w-100" style="max-width: 520px; touch-action: none;"></canvas>
} else if(ActiveSignatureTab == SignatureTabText) {
<p class="text-muted mb-2">Geben Sie Ihre Unterschrift als Text ein und waehlen Sie eine Schriftart.</p>
<div class="row g-2 mb-2">
<div class="col-12 col-md-7">
<input class="form-control" placeholder="Ihre Unterschrift" value="@TypedSignatureText" @oninput="OnTypedSignatureChanged" />
</div>
<div class="col-12 col-md-5">
<select class="form-select" value="@TypedSignatureFont" @onchange="OnTypedSignatureFontChanged">
@foreach(var font in TypedSignatureFonts) {
<option value="@font.Value" style="font-family: @font.Value">@font.Text</option>
}
</select>
</div>
</div>
<canvas id="receiver-typed-signature-pad" width="520" height="160" class="border rounded bg-white w-100" style="max-width: 520px;"></canvas>
} else {
<p class="text-muted mb-2">Laden Sie ein Bild Ihrer Unterschrift hoch.</p>
<input id="receiver-signature-image-input" class="form-control mb-2" type="file" accept="image/png,image/jpeg,image/webp" />
<canvas id="receiver-image-signature-pad" width="520" height="160" class="border rounded bg-white w-100" style="max-width: 520px;"></canvas>
}
@if(!string.IsNullOrWhiteSpace(PopupValidationMessage)) {
<div class="text-danger mt-2">@PopupValidationMessage</div>
}
@@ -61,12 +100,31 @@
}
@code {
const string SignatureTabDraw = "draw";
const string SignatureTabText = "text";
const string SignatureTabImage = "image";
const string DrawCanvasId = "receiver-signature-pad";
const string TypedCanvasId = "receiver-typed-signature-pad";
const string ImageInputId = "receiver-signature-image-input";
const string ImageCanvasId = "receiver-image-signature-pad";
readonly (string Text, string Value)[] TypedSignatureFonts = {
("Brush Script", "'Brush Script MT', cursive"),
("Segoe Script", "'Segoe Script', cursive"),
("Lucida Handwriting", "'Lucida Handwriting', cursive"),
("Comic Sans", "'Comic Sans MS', cursive"),
("Cursive", "cursive")
};
DxReportViewer reportViewer;
XtraReport? Report;
bool SignatureApplied;
bool SignaturePopupVisible;
string? SignatureValidationMessage;
string? PopupValidationMessage;
string ActiveSignatureTab = SignatureTabDraw;
string TypedSignatureText = string.Empty;
string TypedSignatureFont = "'Brush Script MT', cursive";
int ViewerKey;
protected override async Task OnInitializedAsync() {
@@ -76,17 +134,45 @@
}
async Task OpenSignaturePopupAsync() {
ActiveSignatureTab = SignatureTabDraw;
SignaturePopupVisible = true;
SignatureValidationMessage = null;
PopupValidationMessage = null;
await InvokeAsync(StateHasChanged);
await Task.Delay(50);
await JSRuntime.InvokeVoidAsync("receiverSignature.initialize", "receiver-signature-pad");
await InitializeActiveSignatureTabAsync();
}
async Task SetSignatureTabAsync(string tab) {
ActiveSignatureTab = tab;
PopupValidationMessage = null;
await InvokeAsync(StateHasChanged);
await Task.Delay(50);
await InitializeActiveSignatureTabAsync();
}
async Task InitializeActiveSignatureTabAsync() {
if(ActiveSignatureTab == SignatureTabDraw) {
await JSRuntime.InvokeVoidAsync("receiverSignature.initialize", DrawCanvasId);
} else if(ActiveSignatureTab == SignatureTabText) {
await JSRuntime.InvokeVoidAsync("receiverSignature.initializeTyped", TypedCanvasId);
await RenderTypedSignatureAsync();
} else {
await JSRuntime.InvokeVoidAsync("receiverSignature.initializeImage", ImageInputId, ImageCanvasId);
}
}
async Task RenewSignatureAsync() {
PopupValidationMessage = null;
await JSRuntime.InvokeVoidAsync("receiverSignature.clear", "receiver-signature-pad");
if(ActiveSignatureTab == SignatureTabDraw) {
await JSRuntime.InvokeVoidAsync("receiverSignature.clear", DrawCanvasId);
} else if(ActiveSignatureTab == SignatureTabText) {
TypedSignatureText = string.Empty;
await JSRuntime.InvokeVoidAsync("receiverSignature.clearTyped", TypedCanvasId);
} else {
await JSRuntime.InvokeVoidAsync("receiverSignature.clearImage", ImageInputId, ImageCanvasId);
}
}
void CloseSignaturePopup() {
@@ -94,8 +180,22 @@
SignaturePopupVisible = false;
}
async Task OnTypedSignatureChanged(Microsoft.AspNetCore.Components.ChangeEventArgs args) {
TypedSignatureText = args.Value?.ToString() ?? string.Empty;
await RenderTypedSignatureAsync();
}
async Task OnTypedSignatureFontChanged(Microsoft.AspNetCore.Components.ChangeEventArgs args) {
TypedSignatureFont = args.Value?.ToString() ?? TypedSignatureFont;
await RenderTypedSignatureAsync();
}
async Task RenderTypedSignatureAsync() {
await JSRuntime.InvokeVoidAsync("receiverSignature.renderTypedSignature", TypedCanvasId, TypedSignatureText, TypedSignatureFont);
}
async Task ApplySignatureAsync() {
var signatureDataUrl = await JSRuntime.InvokeAsync<string?>("receiverSignature.getDataUrl", "receiver-signature-pad");
var signatureDataUrl = await GetActiveSignatureDataUrlAsync();
if(string.IsNullOrWhiteSpace(signatureDataUrl)) {
PopupValidationMessage = "Die Unterschrift ist fuer den PDF-Export erforderlich.";
@@ -110,6 +210,18 @@
ViewerKey++;
}
async Task<string?> GetActiveSignatureDataUrlAsync() {
if(ActiveSignatureTab == SignatureTabDraw)
return await JSRuntime.InvokeAsync<string?>("receiverSignature.getDataUrl", DrawCanvasId);
if(ActiveSignatureTab == SignatureTabText) {
await RenderTypedSignatureAsync();
return await JSRuntime.InvokeAsync<string?>("receiverSignature.getTypedDataUrl", TypedCanvasId);
}
return await JSRuntime.InvokeAsync<string?>("receiverSignature.getImageDataUrl", ImageCanvasId);
}
async Task ExportSignedPdfAsync() {
if(!SignatureApplied || Report is null) {
SignatureValidationMessage = "Bitte fuegen Sie die Unterschrift zuerst zum Bericht hinzu.";
@@ -180,4 +292,4 @@
foreach(var control in controls)
bottomMargin.Controls.Remove(control);
}
}
}