Add dynamic color support for receivers

Introduced a `Color` property to `ReceiverDraft` and `SignatureFieldDraft` models, enabling dynamic color assignment from a predefined palette (`ReceiverPalette`). Updated the UI to reflect receiver-specific colors in the sender-receiver chips, placement mode hint bar, and signature placement button.

Refactored PDF rendering logic to dynamically derive visual styles (fill, border, and text colors) from receiver colors. Added a `HexToXColor` utility for converting hex color strings to `PdfSharp.Drawing.XColor`.

Removed hardcoded visual styles and replaced them with dynamic, receiver-specific styling. Simplified receiver addition logic to automatically assign colors from the palette. These changes improve clarity and maintainability while enhancing the user experience.
This commit is contained in:
2026-07-01 23:27:38 +02:00
parent 9ecfe08e2e
commit 2c789cd4c0

View File

@@ -74,7 +74,8 @@
<div class="sender-receivers-list"> <div class="sender-receivers-list">
@foreach (var receiver in _receivers) @foreach (var receiver in _receivers)
{ {
<div class="sender-receiver-chip"> <div class="sender-receiver-chip"
style="border-left: 3px solid @receiver.Color;">
<div class="sender-receiver-chip__body"> <div class="sender-receiver-chip__body">
<div class="sender-receiver-chip__name">@receiver.FullName</div> <div class="sender-receiver-chip__name">@receiver.FullName</div>
<div class="sender-receiver-chip__email">@receiver.Email</div> <div class="sender-receiver-chip__email">@receiver.Email</div>
@@ -87,6 +88,7 @@
<button class="pdf-toolbar__btn pdf-toolbar__btn--signature-change sender-toolbar-action-btn sender-toolbar-action-btn--compact <button class="pdf-toolbar__btn pdf-toolbar__btn--signature-change sender-toolbar-action-btn sender-toolbar-action-btn--compact
@(_pendingReceiverForPlacement?.Id == receiver.Id ? "pdf-toolbar__btn--signature-change-active" : "")" @(_pendingReceiverForPlacement?.Id == receiver.Id ? "pdf-toolbar__btn--signature-change-active" : "")"
@onclick="() => ActivatePlacementForReceiver(receiver)" @onclick="() => ActivatePlacementForReceiver(receiver)"
style="@(_pendingReceiverForPlacement?.Id == receiver.Id ? $"background:{receiver.Color};color:#fff;border-color:{receiver.Color};" : $"color:{receiver.Color};border-color:{receiver.Color};")"
title="Signaturfeld platzieren"> title="Signaturfeld platzieren">
<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="M12.146.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1 0 .708l-10 10a.5.5 0 0 1-.168.11l-5 2a.5.5 0 0 1-.65-.65l2-5a.5.5 0 0 1 .11-.168l10-10z" /> <path d="M12.146.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1 0 .708l-10 10a.5.5 0 0 1-.168.11l-5 2a.5.5 0 0 1-.65-.65l2-5a.5.5 0 0 1 .11-.168l10-10z" />
@@ -155,7 +157,7 @@
@* Placement mode hint bar *@ @* Placement mode hint bar *@
@if (_pendingReceiverForPlacement is not null) @if (_pendingReceiverForPlacement is not null)
{ {
<div style="background: #4F46E5; color: white; font-size: 0.75rem; font-weight: 500; <div style="background: @_pendingReceiverForPlacement.Color; color: white; font-size: 0.75rem; font-weight: 500;
padding: 0.3rem 1.5rem; text-align: center; letter-spacing: 0.01em;"> padding: 0.3rem 1.5rem; text-align: center; letter-spacing: 0.01em;">
📌 Klicken Sie auf die Stelle im Dokument für <strong>@_pendingReceiverForPlacement.FullName</strong>. 📌 Klicken Sie auf die Stelle im Dokument für <strong>@_pendingReceiverForPlacement.FullName</strong>.
&nbsp;<button @onclick="CancelPlacement" &nbsp;<button @onclick="CancelPlacement"
@@ -483,7 +485,8 @@
XPt: xPt, XPt: xPt,
YPt: yPt, YPt: yPt,
Page: page1Based, Page: page1Based,
ReceiverName: _pendingReceiverForPlacement.FullName); ReceiverName: _pendingReceiverForPlacement.FullName,
Color: _pendingReceiverForPlacement.Color);
_signatureFields.Add(field); _signatureFields.Add(field);
_pendingReceiverForPlacement = null; _pendingReceiverForPlacement = null;
@@ -565,11 +568,20 @@
var document = PdfSharp.Pdf.IO.PdfReader.Open( var document = PdfSharp.Pdf.IO.PdfReader.Open(
inputMs, PdfSharp.Pdf.IO.PdfDocumentOpenMode.Modify); inputMs, PdfSharp.Pdf.IO.PdfDocumentOpenMode.Modify);
// Visual style — same palette as the receiver-side placeholder foreach (var field in fields)
var fillBrush = new PdfSharp.Drawing.XSolidBrush(PdfSharp.Drawing.XColor.FromArgb( 40, 60, 80, 160)); {
var borderPen = new PdfSharp.Drawing.XPen(PdfSharp.Drawing.XColor.FromArgb(200, 60, 80, 200), 1.5); int pageIndex = field.Page - 1;
var textBrush = new PdfSharp.Drawing.XSolidBrush(PdfSharp.Drawing.XColor.FromArgb(200, 40, 60, 140)); if (pageIndex < 0 || pageIndex >= document.PageCount) continue;
var nameBrush = new PdfSharp.Drawing.XSolidBrush(PdfSharp.Drawing.XColor.FromArgb(255, 30, 30, 100));
var page = document.Pages[pageIndex];
using var gfx = PdfSharp.Drawing.XGraphics.FromPdfPage(page);
// Derive colours from the receiver's hex colour
var fillBrush = new PdfSharp.Drawing.XSolidBrush(HexToXColor(field.Color, alpha: 35));
var borderPen = new PdfSharp.Drawing.XPen(HexToXColor(field.Color, alpha: 210), 1.5);
var textBrush = new PdfSharp.Drawing.XSolidBrush(HexToXColor(field.Color, alpha: 200));
var nameBrush = new PdfSharp.Drawing.XSolidBrush(HexToXColor(field.Color, alpha: 230));
var fontLabel = new PdfSharp.Drawing.XFont("Arial", 9, PdfSharp.Drawing.XFontStyleEx.Bold); var fontLabel = new PdfSharp.Drawing.XFont("Arial", 9, PdfSharp.Drawing.XFontStyleEx.Bold);
var fontName = new PdfSharp.Drawing.XFont("Arial", 7, PdfSharp.Drawing.XFontStyleEx.Regular); var fontName = new PdfSharp.Drawing.XFont("Arial", 7, PdfSharp.Drawing.XFontStyleEx.Regular);
@@ -578,19 +590,6 @@
Alignment = PdfSharp.Drawing.XStringAlignment.Center, Alignment = PdfSharp.Drawing.XStringAlignment.Center,
LineAlignment = PdfSharp.Drawing.XLineAlignment.Center, LineAlignment = PdfSharp.Drawing.XLineAlignment.Center,
}; };
var fmtBottomCenter = new PdfSharp.Drawing.XStringFormat
{
Alignment = PdfSharp.Drawing.XStringAlignment.Center,
LineAlignment = PdfSharp.Drawing.XLineAlignment.Far,
};
foreach (var field in fields)
{
int pageIndex = field.Page - 1;
if (pageIndex < 0 || pageIndex >= document.PageCount) continue;
var page = document.Pages[pageIndex];
using var gfx = PdfSharp.Drawing.XGraphics.FromPdfPage(page);
var rect = new PdfSharp.Drawing.XRect(field.XPt, field.YPt, SigWidthPt, SigHeightPt); var rect = new PdfSharp.Drawing.XRect(field.XPt, field.YPt, SigWidthPt, SigHeightPt);
@@ -616,6 +615,13 @@
return outputMs.ToArray(); return outputMs.ToArray();
} }
/// <summary>Converts a CSS hex colour string (e.g. "#4F46E5") to a PdfSharp XColor.</summary>
static PdfSharp.Drawing.XColor HexToXColor(string hex, int alpha)
{
var c = System.Drawing.ColorTranslator.FromHtml(hex);
return PdfSharp.Drawing.XColor.FromArgb(alpha, c.R, c.G, c.B);
}
// ── Receiver popup ── // ── Receiver popup ──
void OpenAddReceiverPopup() void OpenAddReceiverPopup()
{ {
@@ -777,22 +783,36 @@
return Task.CompletedTask; return Task.CompletedTask;
} }
_receivers.Add(new ReceiverDraft(Guid.NewGuid(), fullName, email, phoneNumber)); _receivers.Add(new ReceiverDraft(Guid.NewGuid(), fullName, email, phoneNumber,
ReceiverPalette[_receivers.Count % ReceiverPalette.Length]));
PersistSession(); PersistSession();
CloseAddReceiverPopup(); CloseAddReceiverPopup();
return Task.CompletedTask; return Task.CompletedTask;
} }
// ── Models ── // ── Models ──
record SignatureFieldDraft(double XPt, double YPt, int Page, string ReceiverName); record SignatureFieldDraft(double XPt, double YPt, int Page, string ReceiverName, string Color);
record NormalisedCoords(double NormX, double NormY, int PageIndex); record NormalisedCoords(double NormX, double NormY, int PageIndex);
record ReceiverDraft(Guid Id, string FullName, string Email, string PhoneNumber); record ReceiverDraft(Guid Id, string FullName, string Email, string PhoneNumber, string Color);
record EditorSessionData( record EditorSessionData(
byte[] OriginalPdfBytes, byte[] OriginalPdfBytes,
List<SignatureFieldDraft> Fields, List<SignatureFieldDraft> Fields,
string FileName, string FileName,
List<ReceiverDraft> Receivers); List<ReceiverDraft> Receivers);
// ── Receiver colour palette (cycles when > 8 receivers) ──
static readonly string[] ReceiverPalette =
[
"#4F46E5", // indigo
"#059669", // emerald
"#DC2626", // red
"#D97706", // amber
"#7C3AED", // violet
"#0891B2", // cyan
"#BE185D", // pink
"#65A30D", // lime
];
} }